supabase_client = AIVectorSearch_Supabase_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_health_fix_notice']); add_action('admin_init', [$this, 'handle_health_fix_dismiss']); add_action('wp_ajax_aivesese_toggle_help', [$this, 'handle_help_toggle']); } public function register_settings() { $settings = [ '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)', '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', 'Supabase connection', '__return_false', 'aivesese'); $this->add_settings_fields(); } private function register_setting(string $id) { $sanitizers = [ '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 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'; // Default OFF for Woodmart } register_setting('aivesese_settings', "aivesese_{$id}", $config); } private function add_settings_fields() { $text_fields = ['url', 'key', 'store', 'openai']; $checkbox_fields = [ 'semantic_toggle' => 'Enable semantic (vector) search - Better relevance (needs OpenAI key)', '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 ($text_fields as $id) { add_settings_field( "aivesese_{$id}", ucfirst(str_replace('_', ' ', $id)), [$this, 'render_text_field'], 'aivesese', 'aivesese_section', ['field_id' => $id] ); } 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_text_field($args) { $field_id = $args['field_id']; $value = get_option("aivesese_{$field_id}"); printf( '', esc_attr($field_id), esc_attr($value) ); } 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) ); } 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_settings_page() { echo '
' . esc_html__('Configure the connection to your Supabase project and optionally enable semantic search using OpenAI.', 'ai-vector-search-semantic') . '
'; $this->render_help_section(); echo ''; 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 '
'; 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 '' . 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 '' . 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.', '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)) { return file_get_contents($path); } } 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 show_health_fix_notice() { if (!current_user_can('manage_options') || get_option('aivesese_health_fix_dismissed')) { return; } $local = (int) wp_count_posts('product')->publish; $remote = 0; $resp = $this->supabase_client->request( 'POST', '/rest/v1/rpc/store_health_check', ['check_store_id' => get_option('aivesese_store_id')] ); if ($resp && isset($resp[0]['published_products'])) { $remote = (int) $resp[0]['published_products']; } if ($local === 0 || $remote > 0) { return; } $this->render_health_fix_notice_content($local); } private function render_health_fix_notice_content(int $local_count) { $sql = $this->get_health_fix_sql(); echo 'AI Vector Search - Dashboard is broken ⚠️
'; echo 'Supabase is still reporting 0 published products while WooCommerce has ' .
esc_html($local_count) . '. We screwed up the SQL function. Run the snippet below once, then reload this page.
⏳ Processing... Please wait.