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'); ?>
Your API service is connected and ready!
Don't have a license? Get one here
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 '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 ''; echo 'No license key configured. Please go to Settings to activate your license.
Unable to connect to API service. Please check your license key.
✅ Connected to ZZZ Solutions API Service
| 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']))) . ' |
'; 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 '
'; 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 '
' . esc_html__('✅ Successfully connected to Supabase!', 'ai-vector-search-semantic') . '
| Metric | Count | Status |
|---|
| ' . esc_html($label) . ' | '; echo '';
if (in_array($label, ['Store ID', 'Supabase URL'])) {
echo '' . esc_html($value) . '';
} else {
echo wp_kses_post($value);
}
echo ' | ';
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!
Sync failed: ' . esc_html($result['message']) . '
Successfully synced batch: ' . esc_attr($result['synced']) . '/' . esc_attr($result['total']) . ' products (offset: ' . esc_attr($offset) . ')
Batch sync failed: ' . esc_html($result['message']) . '
No products without embeddings found in Supabase.
Generated embeddings for ' . esc_html($result['updated']) . ' products. Skipped ' . esc_html($result['skipped']) . '.
' . esc_html($result['message']) . '
Continue with next batch:
'; 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 ' |
Sync all WooCommerce products to Supabase. This may take a while for large catalogs.
'; echo ''; echo 'Sync products in smaller batches to avoid timeouts.
'; echo ''; echo 'Generate or update OpenAI embeddings for products that don\'t have them.
'; echo ''; echo '🆕 SQL Updated! New version includes partial SKU search and Woodmart integration support.
'; 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 'New in this version:
'; echo 'sku_search() function for partial SKU matchingfts_search() with better ranking' . esc_html__('Could not find supabase.sql. Place it at assets/sql/supabase.sql (recommended).', 'ai-vector-search-semantic') . '
'; 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 ''; } 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.
New features added! We\'ve enhanced the search with:
'; echo 'Action required: Please update your Supabase SQL to get these features:
'; echo ''; echo 'Update SQL Now '; echo 'I\'ve Updated It'; echo '
'; echo '