supabase_client = AIVectorSearch_Supabase_Client::instance(); $this->api_client = AIVectorSearch_API_Client::instance(); $this->product_sync = AIVectorSearch_Product_Sync::instance(); $this->init_hooks(); } private function init_hooks() { add_action('admin_init', [$this, 'register_settings']); add_action('admin_menu', [$this, 'add_admin_pages']); add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']); add_action('admin_notices', [$this, 'show_services_banner']); add_action('admin_notices', [$this, 'show_sql_update_notice']); add_action('admin_init', [$this, 'handle_sql_update_dismiss']); add_action('wp_ajax_aivesese_toggle_help', [$this, 'handle_help_toggle']); add_action('wp_ajax_aivesese_activate_license', [$this, 'handle_license_activation']); add_action('wp_ajax_aivesese_test_connection', [$this, 'handle_test_connection']); } public function register_settings() { $settings = [ // Connection mode 'connection_mode' => 'Connection Type', // API mode settings 'license_key' => 'License Key (for API mode)', // Self-hosted mode settings (existing) 'url' => 'Supabase URL (https://xyz.supabase.co)', 'key' => 'Supabase service / anon key', 'store' => 'Store ID (UUID)', 'openai' => 'OpenAI API key (only if semantic search is enabled)', // Feature toggles 'semantic_toggle' => 'Enable semantic (vector) search', 'auto_sync' => 'Auto-sync products on save', 'enable_pdp_similar' => 'PDP "Similar products"', 'enable_cart_below' => 'Below-cart recommendations', 'enable_woodmart_integration' => 'Woodmart live search integration', ]; foreach ($settings as $id => $label) { $this->register_setting($id); } add_settings_section('aivesese_section', 'AI Search Configuration', '__return_false', 'aivesese'); $this->add_settings_fields(); } private function register_setting(string $id) { $sanitizers = [ 'connection_mode' => 'sanitize_text_field', 'license_key' => 'aivesese_passthru', 'url' => 'esc_url_raw', 'key' => 'aivesese_passthru', 'store' => 'sanitize_text_field', 'openai' => 'aivesese_passthru', ]; $config = [ 'type' => 'string', 'sanitize_callback' => $sanitizers[$id] ?? 'sanitize_text_field', 'default' => '', ]; // Special handling for connection mode if ($id === 'connection_mode') { $config['default'] = 'self_hosted'; } // Special handling for checkboxes if (in_array($id, ['semantic_toggle', 'auto_sync', 'enable_pdp_similar', 'enable_cart_below', 'enable_woodmart_integration'])) { $config['sanitize_callback'] = function($v) { return $v === '1' ? '1' : '0'; }; $config['default'] = $id === 'enable_woodmart_integration' ? '0' : '1'; } register_setting('aivesese_settings', "aivesese_{$id}", $config); } private function add_settings_fields() { // Connection mode selector add_settings_field( 'aivesese_connection_mode', 'Connection Type', [$this, 'render_connection_mode_field'], 'aivesese', 'aivesese_section' ); // API mode fields add_settings_field( 'aivesese_license_key', 'License Key', [$this, 'render_license_key_field'], 'aivesese', 'aivesese_section' ); // Self-hosted fields $self_hosted_fields = [ 'url' => 'Supabase URL', 'key' => 'Supabase Service Key', 'store' => 'Store ID (UUID)', 'openai' => 'OpenAI API Key' ]; foreach ($self_hosted_fields as $id => $label) { add_settings_field( "aivesese_{$id}", $label, [$this, 'render_text_field'], 'aivesese', 'aivesese_section', ['field_id' => $id, 'conditional' => 'self_hosted'] ); } // Feature toggles $checkbox_fields = [ 'semantic_toggle' => 'Enable semantic (vector) search - Better relevance', 'auto_sync' => 'Auto-sync products - Automatically sync products when saved/updated', 'enable_pdp_similar' => 'PDP "Similar products" - Show similar products on product pages', 'enable_cart_below' => 'Below-cart recommendations - Show recommendations under cart', 'enable_woodmart_integration' => 'Woodmart live search integration - Enable AI search for Woodmart AJAX search', ]; foreach ($checkbox_fields as $id => $label) { add_settings_field( "aivesese_{$id}", $label, [$this, 'render_checkbox_field'], 'aivesese', 'aivesese_section', ['field_id' => $id, 'label' => $label] ); } } public function render_connection_mode_field() { $value = get_option('aivesese_connection_mode', 'self_hosted'); ?>
License Active

Your API service is connected and ready!

Don't have a license? Get one here

', esc_attr($field_id), esc_attr($field_id), esc_attr($value) ); if ($field_id === 'openai') { echo '

Only required if semantic search is enabled

'; } } public function render_checkbox_field($args) { $field_id = $args['field_id']; $label = $args['label']; $value = get_option("aivesese_{$field_id}"); printf( '', esc_attr($field_id), checked($value, '1', false), esc_html($label) ); } /** * Handle license activation AJAX */ public function handle_license_activation() { check_ajax_referer('aivesese_license_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Unauthorized']); return; } $license_key = sanitize_text_field($_POST['license_key']); if (empty($license_key)) { wp_send_json_error(['message' => 'License key is required']); return; } // Test the license key with the API $result = $this->api_client->activate_license($license_key); if ($result['success']) { // Save license and switch to API mode update_option('aivesese_license_key', $license_key); update_option('aivesese_connection_mode', 'api'); update_option('aivesese_api_activated', '1'); update_option('aivesese_store', $result['store_id']); wp_send_json_success([ 'message' => 'License activated successfully!', 'store_id' => $result['store_id'], 'plan' => $result['plan'] ]); } else { wp_send_json_error(['message' => $result['message']]); } } /** * Enhanced settings page with custom field rendering */ public function render_settings_page() { $connection_mode = get_option('aivesese_connection_mode', 'self_hosted'); echo '
'; echo '

' . esc_html__('AI Vector Search Settings', 'ai-vector-search-semantic') . '

'; // Show different descriptions based on mode if ($connection_mode === 'api') { echo '

You are using our managed API service. No additional setup required!

'; } else { echo '

Configure your own Supabase project and optionally enable semantic search using OpenAI.

'; } // Show help section only for self-hosted mode if ($connection_mode === 'self_hosted') { $this->render_help_section(); } echo '
'; settings_fields('aivesese_settings'); do_settings_sections('aivesese'); submit_button(); echo '
'; echo '
'; // Add styles $this->add_admin_styles(); } private function add_admin_styles() { ?> '; echo '

' . esc_html__('AI Vector Search Status', 'ai-vector-search-semantic') . '

'; $connection_mode = get_option('aivesese_connection_mode', 'self_hosted'); if ($connection_mode === 'api') { $this->render_api_status(); } else { $this->render_self_hosted_status(); } echo ''; } private function render_api_status() { $license_key = get_option('aivesese_license_key'); if (empty($license_key)) { echo '

No license key configured. Please go to Settings to activate your license.

'; return; } // Get status from API $status = $this->api_client->get_status(); if (!$status) { echo '

Unable to connect to API service. Please check your license key.

'; return; } echo '

✅ Connected to ZZZ Solutions API Service

'; // Show subscription info echo '

Subscription Status

'; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; if (!empty($status['expires_at'])) { echo ''; } echo '
Plan' . esc_html(ucfirst($status['plan'])) . '
Status' . esc_html(ucfirst($status['status'])) . '
Products Synced' . number_format($status['products_count']) . '
Searches This Month' . number_format($status['usage']['searches_this_month']) . '
API Calls Today' . number_format($status['usage']['api_calls_today']) . '
Next Payment' . esc_html(date('M j, Y', strtotime($status['expires_at']))) . '
'; // Show usage limits if (!empty($status['limits'])) { echo '

Usage Limits

'; echo '
'; $this->render_usage_bar( 'Products', $status['products_count'], $status['limits']['products_limit'], 'products' ); $this->render_usage_bar( 'Monthly Searches', $status['usage']['searches_this_month'], $status['limits']['searches_limit'], 'searches' ); echo '
'; } } private function render_usage_bar($label, $current, $limit, $type) { $percentage = $limit > 0 ? min(($current / $limit) * 100, 100) : 0; $color = $percentage > 90 ? '#dc2626' : ($percentage > 70 ? '#f59e0b' : '#10b981'); echo '
'; echo '
'; echo '' . esc_html($label) . ''; echo '' . number_format($current) . ($limit > 0 ? ' / ' . number_format($limit) : '') . ''; echo '
'; echo '
'; echo '
'; echo '
'; echo '
'; } private function render_self_hosted_status() { if (!$this->is_configured()) { $this->render_configuration_error(); return; } $health = $this->supabase_client->get_store_health(); if (empty($health)) { $this->render_connection_error(); return; } $this->render_health_overview($health[0]); $this->render_configuration_summary(); $this->render_quick_actions(); } public function add_admin_pages() { add_options_page( 'AI Supabase', 'AI Supabase', 'manage_options', 'aivesese', [$this, 'render_settings_page'] ); add_submenu_page( 'options-general.php', 'AI Supabase Status', 'Supabase Status', 'manage_options', 'aivesese-status', [$this, 'render_status_page'] ); add_submenu_page( 'options-general.php', 'Sync Products to Supabase', 'Sync Products', 'manage_options', 'aivesese-sync', [$this, 'render_sync_page'] ); } public function render_sync_page() { echo '
'; echo '

' . esc_html__('Sync Products to Supabase', 'ai-vector-search-semantic') . '

'; if (!$this->is_configured()) { $this->render_configuration_error(); echo '
'; return; } $this->handle_sync_actions(); $this->render_sync_overview(); $this->render_sync_actions(); echo ''; } private function is_configured(): bool { return get_option('aivesese_store') && get_option('aivesese_url') && get_option('aivesese_key'); } private function render_configuration_error() { echo '

'; echo esc_html__('Configuration incomplete! Please configure your Supabase settings first.', 'ai-vector-search-semantic'); echo ' '; echo esc_html__('Go to Settings', 'ai-vector-search-semantic') . ''; echo '

'; } private function render_connection_error() { echo '

'; echo esc_html__('Unable to connect to Supabase or no data found. Check your configuration and ensure the SQL has been installed.', 'ai-vector-search-semantic'); echo '

'; } private function render_health_overview(array $data) { echo '

' . esc_html__('✅ Successfully connected to Supabase!', 'ai-vector-search-semantic') . '

'; echo '

' . esc_html__('Store Health Overview', 'ai-vector-search-semantic') . '

'; echo ''; echo ''; echo ''; $total = intval($data['total_products']); $published = intval($data['published_products']); $in_stock = intval($data['in_stock_products']); $with_embeddings = intval($data['with_embeddings']); $this->render_health_row('Total Products', $total, $total > 0); $this->render_health_row('Published Products', $published, $published > 0); $this->render_health_row('In Stock Products', $in_stock, $in_stock > 0); $this->render_embeddings_status_row($with_embeddings, $published); echo '
MetricCountStatus
'; } private function render_health_row(string $label, int $count, bool $is_good) { echo ''; echo '' . esc_html($label) . ''; echo '' . number_format($count) . ''; echo '' . ($is_good ? '✅' : '⚠️') . ''; echo ''; } private function render_embeddings_status_row(int $with_embeddings, int $published) { echo ''; echo 'With Embeddings'; echo '' . number_format($with_embeddings) . ''; echo ''; if ($with_embeddings == 0) { echo '❌ No embeddings found'; } elseif ($with_embeddings == $published) { echo '✅ All products have embeddings'; } else { $percent = round(($with_embeddings / $published) * 100, 1); printf(esc_html__('⚠️ %s%% coverage', 'ai-vector-search-semantic'), esc_html($percent)); } echo ''; } private function render_configuration_summary() { echo '

' . esc_html__('Configuration Summary', 'ai-vector-search-semantic') . '

'; echo ''; echo ''; $config_items = [ 'Store ID' => get_option('aivesese_store'), 'Supabase URL' => get_option('aivesese_url'), 'Semantic Search' => get_option('aivesese_semantic_toggle') === '1' ? '✅ Enabled' : '❌ Disabled', 'OpenAI Key' => get_option('aivesese_openai') ? '✅ Configured' : '❌ Not set', 'Woodmart Integration' => $this->get_woodmart_status(), ]; foreach ($config_items as $label => $value) { echo ''; echo ''; echo ''; echo ''; } echo '
' . esc_html($label) . ''; if (in_array($label, ['Store ID', 'Supabase URL'])) { echo '' . esc_html($value) . ''; } else { echo wp_kses_post($value); } echo '
'; } private function get_woodmart_status(): string { $is_enabled = get_option('aivesese_enable_woodmart_integration', '0') === '1'; $is_woodmart_active = defined('WOODMART_THEME_DIR') || wp_get_theme()->get('Name') === 'Woodmart'; if (!$is_woodmart_active) { return '⚪ Woodmart not detected'; } if ($is_enabled) { return '✅ Enabled (Woodmart detected)'; } return '❌ Disabled (Woodmart available)'; } private function render_quick_actions() { echo '

' . esc_html__('Quick Actions', 'ai-vector-search-semantic') . '

'; echo '

'; echo '' . esc_html__('Configure Settings', 'ai-vector-search-semantic') . ' '; echo '' . esc_html__('Refresh Status', 'ai-vector-search-semantic') . ''; echo '

'; } private function handle_sync_actions() { if (!isset($_POST['action']) || !check_admin_referer('aivesese_sync')) { return; } $action = sanitize_key(wp_unslash($_POST['action'])); switch ($action) { case 'sync_all': $this->handle_sync_all(); break; case 'sync_batch': $this->handle_sync_batch(); break; case 'generate_embeddings': $this->handle_generate_embeddings(); break; } } private function handle_sync_all() { $result = $this->product_sync->sync_all_products(); if ($result['success']) { echo '

Successfully synced ' . esc_attr($result['synced']) . '/' . esc_attr($result['total']) . ' products to Supabase!

'; } else { echo '

Sync failed: ' . esc_html($result['message']) . '

'; } } private function handle_sync_batch() { $batch_size = isset($_POST['batch_size']) ? absint(wp_unslash($_POST['batch_size'])) : 50; $offset = isset($_POST['offset']) ? absint(wp_unslash($_POST['offset'])) : 0; $result = $this->product_sync->sync_products_batch($batch_size, $offset); if ($result['success']) { echo '

Successfully synced batch: ' . esc_attr($result['synced']) . '/' . esc_attr($result['total']) . ' products (offset: ' . esc_attr($offset) . ')

'; $this->render_next_batch_form($batch_size, $offset + $batch_size); } else { echo '

Batch sync failed: ' . esc_html($result['message']) . '

'; } } private function handle_generate_embeddings() { $result = $this->product_sync->generate_missing_embeddings(); if ($result['success']) { if ($result['updated'] === 0) { echo '

No products without embeddings found in Supabase.

'; } else { echo '

Generated embeddings for ' . esc_html($result['updated']) . ' products. Skipped ' . esc_html($result['skipped']) . '.

'; } } else { echo '

' . esc_html($result['message']) . '

'; } } private function render_next_batch_form(int $batch_size, int $next_offset) { echo '
'; echo '

Continue with next batch:

'; echo '
'; wp_nonce_field('aivesese_sync'); echo ''; echo ''; echo ''; echo ''; echo '
'; echo '
'; } private function render_sync_overview() { $total_products = wp_count_posts('product')->publish; $synced_count = $this->supabase_client->get_synced_count(); echo '

' . esc_html__('Sync Overview', 'ai-vector-search-semantic') . '

'; echo ''; echo ''; echo ''; echo ''; echo ''; echo '
WooCommerce Products' . number_format($total_products) . '
Synced to Supabase' . number_format($synced_count) . '
Sync Status'; if ($synced_count == 0) { echo '❌ No products synced'; } elseif ($synced_count >= $total_products) { echo '✅ All products synced'; } else { $percent = round(($synced_count / $total_products) * 100, 1); printf( esc_html__('⚠️ %1$s%% synced (%2$d/%3$d)', 'ai-vector-search-semantic'), esc_html($percent), absint($synced_count), absint($total_products) ); } echo '
'; } private function render_sync_actions() { echo '

' . esc_html__('Sync Actions', 'ai-vector-search-semantic') . '

'; // Full sync echo '
'; echo '

🔄 Full Sync

'; echo '

Sync all WooCommerce products to Supabase. This may take a while for large catalogs.

'; echo '
'; wp_nonce_field('aivesese_sync'); echo ''; echo ''; echo '
'; echo '
'; // Batch sync echo '
'; echo '

⚡ Batch Sync

'; echo '

Sync products in smaller batches to avoid timeouts.

'; echo '
'; wp_nonce_field('aivesese_sync'); echo ''; echo ' '; echo ' '; echo ''; echo '
'; echo '
'; // Embeddings generation if (get_option('aivesese_semantic_toggle') === '1' && get_option('aivesese_openai')) { echo '
'; echo '

🧠 Generate Embeddings

'; echo '

Generate or update OpenAI embeddings for products that don\'t have them.

'; echo '
'; wp_nonce_field('aivesese_sync'); echo ''; echo ''; echo '
'; echo '
'; } echo '
'; } private function render_help_section() { $user_id = get_current_user_id(); $is_open = get_user_meta($user_id, '_aivesese_help_open', true); $is_open = ($is_open === '' ? '1' : $is_open); $open_attr = ($is_open === '1') ? ' open' : ''; echo '
'; echo '
'; echo 'Setup Guide click to expand/collapse'; $this->render_setup_instructions(); $this->render_sql_section(); echo '
'; } private function render_setup_instructions() { echo '
'; echo '

🆕 SQL Updated! New version includes partial SKU search and Woodmart integration support.

'; echo '
'; echo '

' . esc_html__('How to find your Supabase credentials:', 'ai-vector-search-semantic') . '

'; echo '
    '; echo '
  1. ' . sprintf( esc_html__('Go to your Supabase project dashboard at %s.', 'ai-vector-search-semantic'), 'https://app.supabase.io/' ) . '
  2. '; echo '
  3. ' . esc_html__('Navigate to "Project Settings" > "API".', 'ai-vector-search-semantic') . '
  4. '; echo '
  5. ' . esc_html__('Your Supabase URL is the "URL" value (e.g., https://xyz.supabase.co).', 'ai-vector-search-semantic') . '
  6. '; echo '
  7. ' . esc_html__('Your Supabase service role key or anon key can be found under "Project API keys".', 'ai-vector-search-semantic') . '
  8. '; echo '
'; echo '

' . esc_html__('How to find your OpenAI API key:', 'ai-vector-search-semantic') . '

'; echo '

' . sprintf( esc_html__('If you enable semantic search, you will need an OpenAI API key from %s.', 'ai-vector-search-semantic'), 'OpenAI website' ) . '

'; } private function render_sql_section() { $sql_content = $this->get_sql_content(); echo '
'; echo '
'; echo '

🎉 Enhanced SQL Schema (v2.0)

'; echo '

New in this version:

'; echo ''; echo '
'; echo '

' . esc_html__('Install/Update the SQL in Supabase', 'ai-vector-search-semantic') . '

'; echo '
    '; echo '
  1. ' . esc_html__('Open your Supabase project → SQL Editor → New query.', 'ai-vector-search-semantic') . '
  2. '; echo '
  3. ' . esc_html__('Click "Copy SQL" below and paste it into the editor.', 'ai-vector-search-semantic') . '
  4. '; echo '
  5. ' . esc_html__('Press RUN and wait for success.', 'ai-vector-search-semantic') . '
  6. '; echo '
  7. ' . esc_html__('✅ Safe to re-run: This SQL uses CREATE OR REPLACE and IF NOT EXISTS.', 'ai-vector-search-semantic') . '
  8. '; echo '
'; if (!$sql_content) { echo '

' . esc_html__('Could not find supabase.sql. Place it at assets/sql/supabase.sql (recommended).', 'ai-vector-search-semantic') . '

'; } else { echo '

'; echo '' . esc_html__('Paste into Supabase → SQL Editor and run it.', 'ai-vector-search-semantic') . '

'; echo ''; echo ''; } } private function get_sql_content(): string { $base = plugin_dir_path(__FILE__); $candidates = [ $base . '../assets/sql/supabase.sql', $base . '../admin/sql/supabase.sql', $base . '../supabase.sql', ]; foreach ($candidates as $path) { if (file_exists($path)) { $content = file_get_contents($path); // Add version header if not present if (strpos($content, '-- AI Vector Search SQL v2.0') === false) { $version_header = "-- AI Vector Search SQL v2.0 - Updated with SKU search and enhanced FTS\n" . "-- Run this entire script in Supabase SQL Editor\n" . "-- New features: Partial SKU search, Better ranking, Woodmart integration\n\n"; $content = $version_header . $content; } return $content; } } return ''; } public function enqueue_admin_assets($hook) { $page = isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : ''; if (!in_array($page, ['aivesese', 'aivesese-status', 'aivesese-sync'], true)) { return; } $this->enqueue_help_script(); $this->enqueue_admin_styles(); $this->enqueue_admin_scripts(); } private function enqueue_help_script() { wp_register_script('aivesese-help', false, [], AIVESESE_PLUGIN_VERSION, true); wp_enqueue_script('aivesese-help'); $data = [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aivesese_help_nonce'), ]; wp_add_inline_script('aivesese-help', 'window.AISupabaseHelp=' . wp_json_encode($data) . ';', 'before'); $help_script = $this->get_help_toggle_script(); wp_add_inline_script('aivesese-help', $help_script); } private function enqueue_admin_styles() { wp_register_style('aivesese-admin', false, [], AIVESESE_PLUGIN_VERSION); wp_enqueue_style('aivesese-admin'); $admin_css = $this->get_admin_css(); wp_add_inline_style('aivesese-admin', $admin_css); } private function enqueue_admin_scripts() { wp_register_script('aivesese-admin', false, [], AIVESESE_PLUGIN_VERSION, true); wp_enqueue_script('aivesese-admin'); $copy_script = $this->get_copy_sql_script(); $submit_script = $this->get_submit_spinner_script(); wp_add_inline_script('aivesese-admin', $copy_script); wp_add_inline_script('aivesese-admin', $submit_script); } public function show_services_banner() { $screen = function_exists('get_current_screen') ? get_current_screen() : null; $allowed_screens = [ 'settings_page_aivesese', 'settings_page_aivesese-status', 'settings_page_aivesese-sync' ]; if (!$screen || !in_array($screen->id, $allowed_screens, true)) { return; } echo '
🚀
Need a hand with AI search or Supabase?
Our team at ZZZ Solutions can install, customise and tune everything for you.
See Services
'; } public function handle_help_toggle() { check_ajax_referer('aivesese_help_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'forbidden'], 403); } $open = (isset($_POST['open']) && wp_unslash($_POST['open']) === '1') ? '1' : '0'; update_user_meta(get_current_user_id(), '_aivesese_help_open', $open); wp_send_json_success(['open' => $open]); } // Asset content methods private function get_admin_css(): string { return trim(" .ai-supabase-help details { border:1px solid #dcdcde; border-radius:6px; background:#fff; } .ai-supabase-help__summary { padding:12px 14px; cursor:pointer; list-style:none; } .ai-supabase-help__summary::-webkit-details-marker { display:none; } .ai-supabase-help__summary:after { content:'▾'; float:right; transition:transform .2s ease; } .ai-supabase-help details[open] .ai-supabase-help__summary:after { transform:rotate(180deg); } .ai-supabase-help details > *:not(.ai-supabase-help__summary) { padding:0 14px 14px; } .ai-supabase-help__hint { color:#646970; font-weight:400; margin-left:8px; } "); } private function get_help_toggle_script(): string { return "(function(){ var el = document.getElementById('ai-supabase-help-details'); if (!el) return; el.addEventListener('toggle', function(){ var body = new FormData(); body.append('action', 'aivesese_toggle_help'); body.append('open', el.open ? '1' : '0'); body.append('nonce', window.AISupabaseHelp.nonce); fetch(window.AISupabaseHelp.ajax_url, { method:'POST', credentials:'same-origin', body: body }); }, { passive: true }); })();"; } private function get_copy_sql_script(): string { return "(function(){ var btn = document.getElementById('ai-copy-sql'); var ta = document.getElementById('ai-sql'); var out = document.getElementById('ai-copy-status'); if (!btn || !ta) return; btn.addEventListener('click', async function(){ try { await navigator.clipboard.writeText(ta.value); if(out){ out.textContent = 'SQL copied to clipboard.'; out.style.display='block'; out.style.color='green'; } } catch(e){ ta.select(); document.execCommand('copy'); if(out){ out.textContent = 'Copied using fallback.'; out.style.display='block'; out.style.color='green'; } } }, { passive:true }); })();"; } private function get_submit_spinner_script(): string { return "document.addEventListener('DOMContentLoaded', function(){ var forms = document.querySelectorAll('form'); forms.forEach(function(form){ form.addEventListener('submit', function(){ var button = form.querySelector('button[type=submit]'); if (button) { button.innerHTML = 'Processing...'; button.disabled = true; } var statusDiv = document.getElementById('sync-status'); if (statusDiv) { statusDiv.innerHTML = '

⏳ Processing... Please wait.

'; } }); }); });"; } public function show_sql_update_notice() { if (!current_user_can('manage_options') || get_option('aivesese_sql_v2_dismissed')) { return; } // Only show on relevant admin pages $screen = function_exists('get_current_screen') ? get_current_screen() : null; $allowed_screens = [ 'settings_page_aivesese', 'settings_page_aivesese-status', 'settings_page_aivesese-sync', 'plugins' ]; if (!$screen || !in_array($screen->id, $allowed_screens, true)) { return; } echo '
'; echo '

🔄 AI Vector Search - SQL Update Required

'; echo '

New features added! We\'ve enhanced the search with:

'; echo ''; echo '

Action required: Please update your Supabase SQL to get these features:

'; echo '
    '; echo '
  1. Go to Settings → AI Supabase
  2. '; echo '
  3. Expand the "Setup Guide" section
  4. '; echo '
  5. Copy the updated SQL and run it in Supabase → SQL Editor
  6. '; echo '
  7. The new functions will be added/updated automatically
  8. '; echo '
'; echo '

'; echo 'Update SQL Now '; echo 'I\'ve Updated It'; echo '

'; echo '
'; } public function handle_sql_update_dismiss() { if (isset($_GET['aivesese_sql_v2_dismiss']) && check_admin_referer('aivesese_sql_v2_nonce')) { update_option('aivesese_sql_v2_dismissed', time()); wp_safe_redirect(remove_query_arg(['aivesese_sql_v2_dismiss', '_wpnonce'])); exit; } } }