Plugin Directory

Changeset 3476789


Ignore:
Timestamp:
03/07/2026 12:56:39 AM (3 weeks ago)
Author:
visualwebs
Message:

Release 5.5.0 - Complete SaaS Architecture Migration

Major Changes:

  • Complete migration to SaaS architecture (all AI features centralized)
  • New n8n-based chatbot with remote widget loading from SaaS CDN
  • All chatbot configuration now centralized in SaaS (colors, titles, prompts)
  • GDPR compliance: chatbot marked as functional category (no consent required)
  • New event system with source validation (woocommerce/magento)
  • Product and page feed generators for semantic search sync

Improvements:

  • Removed 600+ lines of legacy JavaScript (chatbot.js, dashboard.js, widget.js, semantic-search.js)
  • Removed legacy admin CSS (admin-style.css)
  • Removed legacy templates (ml-widgets.php, dynamic pricing templates, semantic search templates)
  • Removed external composer dependencies (pdfparser, phpword)
  • Cleaned up all legacy API integrations (OpenAI, Pinecone helpers removed)
  • Feed sync now uses entity_id format matching Magento

Bug Fixes:

  • Fixed chatbot configuration loading from SaaS instead of local WordPress
  • Fixed event source field to use 'woocommerce' instead of 'wordpress'
  • Fixed GDPR cookie consent blocking chatbot display
  • Fixed feed sync entity_id mapping

Technical:

See readme.txt changelog for complete details.

Location:
visualwebs-ml
Files:
16 added
28 deleted
41 edited
2 copied

Legend:

Unmodified
Added
Removed
  • visualwebs-ml/tags/5.5.0/class-visualwebs-ml.php

    r3322994 r3476789  
    9494
    9595        add_action( 'plugins_loaded', array( 'Visualwebs\ML\Admin', 'init' ) );
     96        add_action( 'wp_footer', array( $this, 'render_frontend_chatbot_widget' ), 999 );
    9697        add_action(
    9798            'redux/options/visualwebs_ml_options/saved',
     
    135136                register_rest_route(
    136137                    'visualwebs-ml/v1',
     138                    'chat-api/admin-call',
     139                    array(
     140                        'methods'             => 'POST',
     141                        'callback'            => array( $this, 'handle_admin_chat_api_call' ),
     142                        'permission_callback' => array( $this, 'check_admin_chat_permissions' ),
     143                    )
     144                );
     145
     146                register_rest_route(
     147                    'visualwebs-ml/v1',
    137148                    'ml-api/call',
    138149                    array(
     
    156167        require_once __DIR__ . '/includes/cronjobs.php';
    157168        require_once __DIR__ . '/includes/hooks/entity-save.php';
     169        require_once __DIR__ . '/includes/hooks/event-dispatcher.php';
    158170        require_once __DIR__ . '/includes/helpers/content-generator.php';
     171        require_once __DIR__ . '/includes/feeds/class-insights-feed-generator.php';
    159172    }
    160173
     
    174187
    175188    /**
     189     * Render chatbot widget in frontend footer.
     190     *
     191     * @since 5.5.0
     192     */
     193    public function render_frontend_chatbot_widget() {
     194        require plugin_dir_path( __FILE__ ) . 'templates/n8n-chatbot-widget.php';
     195    }
     196
     197    /**
    176198     * Handle chat API callback
    177199     *
     
    189211     */
    190212    public function handle_chat_api_call( WP_REST_Request $request ) {
    191         $response = $this->chatbot_api_helper->execute( $request->get_query_params() );
     213        $response = $this->chatbot_api_helper->execute( $request->get_json_params() );
     214        return rest_ensure_response( $response );
     215    }
     216
     217    /**
     218     * Validate permissions for admin chatbot endpoint.
     219     *
     220     * @param WP_REST_Request $request Request object.
     221     * @return bool|WP_Error
     222     */
     223    public function check_admin_chat_permissions( WP_REST_Request $request ) {
     224        if ( ! is_user_logged_in() || ! current_user_can( 'manage_options' ) ) {
     225            return new WP_Error( 'vwml_forbidden', __( 'You are not allowed to use admin chatbot.', 'visualwebs-ml' ), array( 'status' => 403 ) );
     226        }
     227
     228        $nonce = sanitize_text_field( (string) $request->get_param( '_vwml_nonce' ) );
     229        if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'visualwebs_ml_admin_chat' ) ) {
     230            return new WP_Error( 'vwml_invalid_nonce', __( 'Invalid admin chatbot nonce.', 'visualwebs-ml' ), array( 'status' => 403 ) );
     231        }
     232
     233        return true;
     234    }
     235
     236    /**
     237     * Handle admin chatbot API call routed through secure local endpoint.
     238     *
     239     * @param WP_REST_Request $request The REST API request object.
     240     * @return WP_REST_Response
     241     */
     242    public function handle_admin_chat_api_call( WP_REST_Request $request ) {
     243        $response = $this->chatbot_api_helper->execute_admin( $request->get_json_params() );
    192244        return rest_ensure_response( $response );
    193245    }
  • visualwebs-ml/tags/5.5.0/composer.json

    r3319473 r3476789  
    99        }
    1010    },
    11     "require": {
    12         "smalot/pdfparser": "^0.18",
    13         "phpoffice/phpword": "^1.1"
    14     },
    1511    "scripts": {
    1612        "post-install-cmd": [
  • visualwebs-ml/tags/5.5.0/includes/cronjobs.php

    r3330758 r3476789  
    2323use Visualwebs\ML\Helper\Visualwebs as ApiHelper;
    2424use Visualwebs\ML\Helper\Chatbot as ChatbotHelper;
    25 use Visualwebs\ML\Helper\Pinecone as PineconeHelper;
    26 use Visualwebs\ML\Helper\OpenAi as OpenAiHelper;
    2725
    2826register_activation_hook(
    2927    plugin_dir_path( __DIR__ ) . 'visualwebs-ml.php',
    3028    function () {
    31         if ( ! wp_next_scheduled( 'visualwebs_ml_semantic_queue_processor' ) ) {
    32             wp_schedule_event( time(), 'every_five_minutes', 'visualwebs_ml_semantic_queue_processor' );
    33         }
    34         if ( ! wp_next_scheduled( 'visualwebs_ml_daily_dynamic_pricing' ) ) {
    35             $next_run = strtotime( 'tomorrow 07:00:00' );
    36             wp_schedule_event( $next_run, 'daily', 'visualwebs_ml_daily_dynamic_pricing' );
    37         }
    38         if ( ! wp_next_scheduled( 'visualwebs_ml_check_dynamic_pricing_batch_jobs' ) ) {
    39             wp_schedule_event( time(), 'every_five_minutes', 'visualwebs_ml_check_dynamic_pricing_batch_jobs' );
    40         }
    41         if ( ! wp_next_scheduled( 'visualwebs_ml_weekly_dynamic_pricing_training' ) ) {
    42             $next_run = strtotime( 'tomorrow 00:00:00' );
    43             wp_schedule_event( $next_run, 'weekly', 'visualwebs_ml_weekly_dynamic_pricing_training' );
     29        // Feed generation cron jobs
     30        if ( ! wp_next_scheduled( 'visualwebs_ml_product_feed' ) ) {
     31            wp_schedule_event( time(), 'hourly', 'visualwebs_ml_product_feed' );
     32        }
     33        if ( ! wp_next_scheduled( 'visualwebs_ml_page_feed' ) ) {
     34            $next_run = strtotime( 'tomorrow 02:00:00' );
     35            wp_schedule_event( $next_run, 'daily', 'visualwebs_ml_page_feed' );
     36        }
     37        if ( ! wp_next_scheduled( 'visualwebs_ml_sales_feed' ) ) {
     38            $next_run = strtotime( 'tomorrow 02:00:00' );
     39            wp_schedule_event( $next_run, 'daily', 'visualwebs_ml_sales_feed' );
     40        }
     41        if ( ! wp_next_scheduled( 'visualwebs_ml_insights_feed' ) ) {
     42            $next_run = strtotime( 'tomorrow 03:00:00' );
     43            wp_schedule_event( $next_run, 'daily', 'visualwebs_ml_insights_feed' );
    4444        }
    4545    }
     
    4949    plugin_dir_path( __DIR__ ) . 'visualwebs-ml.php',
    5050    function () {
    51         wp_clear_scheduled_hook( 'visualwebs_ml_semantic_queue_processor' );
    52         wp_clear_scheduled_hook( 'visualwebs_ml_daily_dynamic_pricing' );
    53         wp_clear_scheduled_hook( 'visualwebs_ml_check_dynamic_pricing_batch_jobs' );
    54         wp_clear_scheduled_hook( 'visualwebs_ml_weekly_dynamic_pricing_training' );
    55     }
    56 );
    57 
    58 add_filter(
    59     'cron_schedules', // phpcs:ignore WordPress.WP.CronInterval.CronSchedulesInterval
    60     function ( $schedules ) {
    61         $schedules['every_five_minutes'] = array(
    62             'interval' => 300,
    63             'display'  => __( 'Every 5 Minutes', 'visualwebs-ml' ),
    64         );
    65         return $schedules;
    66     }
    67 );
    68 
    69 add_action(
    70     'visualwebs_ml_semantic_queue_processor',
    71     function () {
    72         global $wpdb;
    73 
    74         $table_name  = $wpdb->prefix . 'visualwebs_ml_semantic_queue';
    75         $batch_limit = 5;
    76 
    77         // Clear completed and disabled jobs.
    78         // phpcs:ignore WordPress.DB.DirectDatabaseQuery
    79         $wpdb->query(
    80             $wpdb->prepare(
    81                 'DELETE FROM %i
    82              WHERE job_status = %d AND job_sync_status = %d',
    83                 $table_name,
    84                 0,
    85                 1
    86             )
    87         );
    88 
    89         // Fetch jobs that are not completed.
    90         // phpcs:ignore WordPress.DB.DirectDatabaseQuery
    91         $jobs = $wpdb->get_results(
    92             $wpdb->prepare(
    93                 'SELECT * FROM %i
    94              WHERE job_sync_status != %d
    95              LIMIT %d',
    96                 $table_name,
    97                 1,
    98                 $batch_limit
    99             )
    100         );
    101 
    102         foreach ( $jobs as $job ) {
    103             // Process each job.
    104             visualwebs_ml_process_queue_item( $job );
    105 
    106             // Mark the job as completed.
    107             // phpcs:ignore WordPress.DB.DirectDatabaseQuery
    108             $wpdb->update(
    109                 $table_name,
    110                 array( 'job_sync_status' => 1 ), // Mark as completed.
    111                 array( 'job_id' => $job->job_id )
    112             );
    113         }
    114     }
    115 );
    116 
    117 add_action(
    118     'visualwebs_ml_daily_dynamic_pricing',
    119     'visualwebs_ml_dynamic_pricing'
     51        wp_clear_scheduled_hook( 'visualwebs_ml_product_feed' );
     52        wp_clear_scheduled_hook( 'visualwebs_ml_page_feed' );
     53        wp_clear_scheduled_hook( 'visualwebs_ml_sales_feed' );
     54        wp_clear_scheduled_hook( 'visualwebs_ml_insights_feed' );
     55    }
    12056);
    12157
     
    250186
    251187add_action(
    252     'visualwebs_ml_weekly_dynamic_pricing_training',
    253     function () {
    254         do_action( 'visualwebs_ml_train_dynamic_pricing' );
    255     }
    256 );
    257 
    258 add_action(
    259188    'visualwebs_ml_train_dynamic_pricing',
    260189    'visualwebs_ml_train_dynamic_pricing'
     
    382311
    383312
    384 add_action(
    385     'visualwebs_ml_check_dynamic_pricing_batch_jobs',
    386     function () {
    387         global $wpdb;
    388         $helper     = new \Visualwebs\ML\Helper\Data();
    389         $api_helper = new ApiHelper();
    390 
    391         if ( ! $helper->getIsDynamicPricingEnabled() ) {
    392             return false;
    393         }
    394         // Fetch all pending jobs.
    395         $table_name = $wpdb->prefix . 'visualwebs_ml_dynamic_pricing_jobs';
    396         // phpcs:ignore WordPress.DB.DirectDatabaseQuery, phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
    397         $pending_jobs = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i WHERE job_status = %s', $table_name, 'processing' ) );
    398         foreach ( $pending_jobs as $job ) {
    399             $job_id   = $job->job_id;
    400             $job_hash = $job->job_hash;
    401             $data     = array(
    402                 'job_hash' => $job_hash,
    403             );
    404 
    405             $response = $api_helper->sendDynamicPricingBatchJobStatusRequest( $data );
    406 
    407             if ( 'done' === $response['status'] && ! empty( $response['job_id'] ) ) {
    408 
    409                 if ( 'predict' === $response['type'] ) {
     313function visualwebs_ml_check_dynamic_pricing_batch_jobs() {
     314    global $wpdb;
     315    $helper     = new \Visualwebs\ML\Helper\Data();
     316    $api_helper = new ApiHelper();
     317
     318    if ( ! $helper->getIsDynamicPricingEnabled() ) {
     319        return false;
     320    }
     321    // Fetch all pending jobs.
     322    $table_name = $wpdb->prefix . 'visualwebs_ml_dynamic_pricing_jobs';
     323    // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
     324    $pending_jobs = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i WHERE job_status = %s', $table_name, 'processing' ) );
     325    foreach ( $pending_jobs as $job ) {
     326        $job_id   = $job->job_id;
     327        $job_hash = $job->job_hash;
     328        $data     = array(
     329            'job_hash' => $job_hash,
     330        );
     331
     332        $response = $api_helper->sendDynamicPricingBatchJobStatusRequest( $data );
     333
     334        if ( 'done' === $response['status'] && ! empty( $response['job_id'] ) ) {
     335
     336            if ( 'predict' === $response['type'] ) {
    410337
    411338                    $results      = $api_helper->downloadDynamicPricingBatchResults( $response['job_id'] );
     
    439366
    440367                        if ( $new_price <= $cogs ) {
    441                             $min_pct   = min( 1, $helper->getDynamicPricingMinProfit() );
     368                            // Default minimum profit 10% (config for min_profit was removed, now managed in SaaS)
     369                            $min_pct   = 10;
    442370                            $new_price = $cogs * ( 1 + ( $min_pct / 100 ) );
    443371                        }
     
    487415        }
    488416    }
    489 );
    490417
    491418/**
     
    500427function visualwebs_ml_process_queue_item( $job ) {
    501428    global $wpdb;
    502     $helper          = new Helper();
    503     $pinecone_helper = new PineconeHelper();
    504     $opean_ai_helper = new OpenAiHelper();
    505     $api_helper      = new ApiHelper();
    506     $chatbot_helper  = new ChatbotHelper();
    507 
    508     $use_customer_api_keys_enabled = $helper->getUseCustomerApiKeysEnabled();
    509     $pinecone_api_key              = $pinecone_helper->getPineconeApiKey();
    510     $pinecone_index_host           = $pinecone_helper->getPineconeIndexHost();
    511     $pinecone_namespace            = $pinecone_helper->getPineconeNamespace();
    512     $openai_api_key                = $opean_ai_helper->getApiKey();
    513     $domain_namespace              = $helper->getDomainNamespace();
    514 
    515     $params = $use_customer_api_keys_enabled ? array(
    516         'openai_api_key'      => $openai_api_key,
    517         'pinecone_api_key'    => $pinecone_api_key,
    518         'pinecone_index_host' => $pinecone_index_host,
    519         'pinecone_namespace'  => $pinecone_namespace,
    520     ) : array(
    521         'pinecone_namespace' => $domain_namespace,
     429    $helper         = new Helper();
     430    $api_helper     = new ApiHelper();
     431    $chatbot_helper = new ChatbotHelper();
     432
     433    // Legacy semantic search processing (now managed in SaaS)
     434    $params = array(
     435        'pinecone_namespace' => $helper->getDomainNamespace(),
    522436    );
    523437
     
    675589    );
    676590}
     591
     592// Feed generation cron job handlers
     593
     594add_action('visualwebs_ml_product_feed', function() {
     595    require_once plugin_dir_path(__FILE__) . 'feeds/class-product-feed-generator.php';
     596    $generator = new VisualwebsML_ProductFeedGenerator();
     597    $generator->generate();
     598});
     599
     600add_action('visualwebs_ml_page_feed', function() {
     601    require_once plugin_dir_path(__FILE__) . 'feeds/class-page-feed-generator.php';
     602    $generator = new VisualwebsML_PageFeedGenerator();
     603    $generator->generate();
     604});
     605
     606add_action('visualwebs_ml_sales_feed', function() {
     607    require_once plugin_dir_path(__FILE__) . 'feeds/class-sales-feed-generator.php';
     608    $generator = new VisualwebsML_SalesFeedGenerator();
     609    $generator->generate();
     610});
     611
     612add_action('visualwebs_ml_insights_feed', function() {
     613    require_once plugin_dir_path(__FILE__) . 'feeds/class-insights-feed-generator.php';
     614    $generator = new VisualwebsML_InsightsFeedGenerator();
     615    $generator->generate();
     616});
  • visualwebs-ml/tags/5.5.0/includes/redux-config.php

    r3325171 r3476789  
    2323}
    2424
    25 $opt_name = 'visualwebs_ml_options';
    26 
    27 $args = array(
    28     'opt_name'           => $opt_name,
     25$visualwebs_ml_opt_name = 'visualwebs_ml_options';
     26
     27$visualwebs_ml_args = array(
     28    'opt_name'           => $visualwebs_ml_opt_name,
    2929    'display_name'       => 'Visualwebs AI Cloud Suite Options',
    3030    'menu_type'          => 'submenu',
     
    5757);
    5858
    59 Redux::setArgs( $opt_name, $args );
     59Redux::setArgs( $visualwebs_ml_opt_name, $visualwebs_ml_args );
    6060
    6161// Extension Options Group.
    6262Redux::setSection(
    63     $opt_name,
     63    $visualwebs_ml_opt_name,
    6464    array(
    6565        'title'  => __( 'Extension Options', 'visualwebs-ml' ),
     
    8282// Visualwebs AI Cloud Suite API Group.
    8383Redux::setSection(
    84     $opt_name,
     84    $visualwebs_ml_opt_name,
    8585    array(
    8686        'title'  => __( 'API', 'visualwebs-ml' ),
     
    107107            ),
    108108            array(
    109                 'id'      => 'use_customer_api_keys',
    110                 'type'    => 'switch',
    111                 'title'   => __( 'Use your own API Keys', 'visualwebs-ml' ),
    112                 'default' => false,
    113                 'on'      => 'Enabled',
    114                 'off'     => 'Disabled',
    115                 'desc'    => __( 'Enable only if you want to use your OpenAI and Pinecone keys.', 'visualwebs-ml' ),
    116             ),
    117         ),
    118     )
    119 );
    120 
    121 // User Widgets Group.
    122 Redux::setSection(
    123     $opt_name,
    124     array(
    125         'title'  => __( 'User Widgets', 'visualwebs-ml' ),
    126         'id'     => 'widgets',
    127         'desc'   => __( 'Settings for User Widgets.', 'visualwebs-ml' ),
    128         'icon'   => 'el el-th-large',
    129         'fields' => array(
    130             array(
    131                 'id'           => 'user_widgets',
    132                 'type'         => 'repeater',
    133                 'group_values' => true,
    134                 'title'        => __( 'Widgets', 'visualwebs-ml' ),
    135                 'desc'         => __( 'Configure user widgets.', 'visualwebs-ml' ),
    136                 'fields'       => array(
    137                     array(
    138                         'id'      => 'widget_title',
    139                         'type'    => 'text',
    140                         'title'   => __( 'Title', 'visualwebs-ml' ),
    141                         'default' => '',
    142                     ),
    143                     array(
    144                         'id'      => 'widget_instructions',
    145                         'type'    => 'text',
    146                         'title'   => __( 'Instructions', 'visualwebs-ml' ),
    147                         'default' => '',
    148                     ),
    149                     array(
    150                         'id'      => 'widget_render_el',
    151                         'type'    => 'text',
    152                         'title'   => __( 'Render Element', 'visualwebs-ml' ),
    153                         'default' => '',
    154                     ),
    155                     array(
    156                         'id'      => 'widget_source_el',
    157                         'type'    => 'text',
    158                         'title'   => __( 'Source Element', 'visualwebs-ml' ),
    159                         'default' => '',
    160                     ),
     109                'id'       => 'store_id',
     110                'type'     => 'text',
     111                'title'    => __( 'Store ID (SaaS UUID)', 'visualwebs-ml' ),
     112                'desc'     => __( 'UUID from SaaS Dashboard when registering this store.', 'visualwebs-ml' ),
     113                'required' => array( 'api_enabled', '=', '1' ),
     114                'validate' => 'no_html',
     115            ),
     116        ),
     117    )
     118);
     119
     120// Workflow Integration Group.
     121Redux::setSection(
     122    $visualwebs_ml_opt_name,
     123    array(
     124        'title'  => __( 'Workflow Integration', 'visualwebs-ml' ),
     125        'id'     => 'workflow',
     126        'desc'   => __( 'Configure event delivery to SaaS and n8n workflows.', 'visualwebs-ml' ),
     127        'icon'   => 'el el-random',
     128        'fields' => array(
     129            array(
     130                'id'       => 'enable_workflows',
     131                'type'     => 'switch',
     132                'title'    => __( 'Enable Workflows', 'visualwebs-ml' ),
     133                'default'  => false,
     134                'on'       => 'Enabled',
     135                'off'      => 'Disabled',
     136                'required' => array( 'api_enabled', '=', '1' ),
     137            ),
     138            array(
     139                'id'       => 'enabled_events',
     140                'type'     => 'checkbox',
     141                'title'    => __( 'Enabled Events', 'visualwebs-ml' ),
     142                'options'  => array(
     143                    'woocommerce_new_order'          => __( 'Order Created', 'visualwebs-ml' ),
     144                    'woocommerce_order_status_changed' => __( 'Order Status Changed', 'visualwebs-ml' ),
     145                    'user_register'                  => __( 'User Registered', 'visualwebs-ml' ),
     146                    'woocommerce_product_updated'    => __( 'Product Updated', 'visualwebs-ml' ),
    161147                ),
    162                 'default'      => array(),
     148                'default'  => array(
     149                    'woocommerce_new_order'          => '1',
     150                    'woocommerce_order_status_changed' => '1',
     151                ),
     152                'required' => array( 'enable_workflows', '=', '1' ),
     153            ),
     154            array(
     155                'id'       => 'anonymize_payload',
     156                'type'     => 'switch',
     157                'title'    => __( 'Anonymize Personal Data (GDPR)', 'visualwebs-ml' ),
     158                'default'  => true,
     159                'on'       => 'Enabled',
     160                'off'      => 'Disabled',
     161                'desc'     => __( 'Redacts personal fields before sending event payloads.', 'visualwebs-ml' ),
     162                'required' => array( 'enable_workflows', '=', '1' ),
    163163            ),
    164164        ),
     
    168168// ChatGPT Group.
    169169Redux::setSection(
    170     $opt_name,
     170    $visualwebs_ml_opt_name,
    171171    array(
    172172        'title'  => __( 'ChatGPT', 'visualwebs-ml' ),
     
    176176        'fields' => array(
    177177            array(
    178                 'id'       => 'chatgpt_api_key',
    179                 'type'     => 'text',
    180                 'title'    => __( 'Open AI Api key', 'visualwebs-ml' ),
    181                 'required' => array( 'use_customer_api_keys', '=', '1' ),
    182             ),
    183             array(
    184178                'id'      => 'chatbot_enabled',
    185179                'type'    => 'switch',
     
    191185            ),
    192186            array(
    193                 'id'      => 'debug_enabled',
    194                 'type'    => 'switch',
    195                 'title'   => __( 'Enables the Chatbot debug', 'visualwebs-ml' ),
    196                 'default' => false,
    197                 'on'      => 'Enabled',
    198                 'off'     => 'Disabled',
    199                 'desc'    => __( 'Writes Chatbot information in logs/visualwebs_ml.log', 'visualwebs-ml' ),
    200             ),
    201             array(
    202                 'id'      => 'chatbot_header_title',
    203                 'type'    => 'text',
    204                 'default' => 'Support',
    205                 'title'   => __( 'Chatbot header', 'visualwebs-ml' ),
    206                 'desc'    => __( 'The title to show in the header of chat widget, i.e. Support, mysite.com Support..', 'visualwebs-ml' ),
    207             ),
    208             array(
    209                 'id'    => 'chatbot_logo',
    210                 'type'  => 'media',
    211                 'title' => __( 'Upload Logo', 'visualwebs-ml' ),
    212                 'desc'  => __( 'Allowed file types: jpg, jpeg, gif, png', 'visualwebs-ml' ),
    213             ),
    214             array(
    215                 'id'       => 'chatbot_main_background_color',
    216                 'type'     => 'color',
    217                 'title'    => __( 'Color', 'visualwebs-ml' ),
    218                 'default'  => '#ffffff',
    219                 'required' => array( 'chatbot_enabled', '=', '1' ),
    220                 'validate' => 'not_empty',
    221             ),
    222             array(
    223                 'id'       => 'chatbot_context',
    224                 'type'     => 'textarea',
    225                 'default'  => 'Act as E-commerce support team',
    226                 'title'    => __( 'Chatbot context', 'visualwebs-ml' ),
    227                 'desc'     => __( 'Provide clear instructions for the chatbot\'s behavior.', 'visualwebs-ml' ),
    228                 'required' => array( 'chatbot_enabled', '=', '1' ),
    229                 'validate' => 'not_empty',
    230             ),
    231             array(
    232                 'id'      => 'chatbot_offline_message',
    233                 'type'    => 'text',
    234                 'default' => 'Our support system is OFF now, please try later.',
    235                 'title'   => __( 'Message to show to customers when Bot is Offline.', 'visualwebs-ml' ),
    236                 'desc'    => __( 'Example: Our support system is OFF now, please try later.', 'visualwebs-ml' ),
    237             ),
    238             array(
    239                 'id'    => 'chatbot_disclaimer_url',
    240                 'type'  => 'text',
    241                 'title' => __( 'Disclaimer url', 'visualwebs-ml' ),
    242                 'desc'  => __( 'Specify the slug of the disclaimer page.', 'visualwebs-ml' ),
    243             ),
    244         ),
    245     )
    246 );
    247 
    248 // Semantic Search Group.
    249 Redux::setSection(
    250     $opt_name,
    251     array(
    252         'title'  => __( 'Semantic Search', 'visualwebs-ml' ),
    253         'id'     => 'semantic_search',
    254         'desc'   => __( 'Settings for Semantic Search.', 'visualwebs-ml' ),
    255         'icon'   => 'el el-search',
    256         'fields' => array(
    257             array(
    258                 'id'      => 'semantic_search_enabled',
    259                 'type'    => 'switch',
    260                 'title'   => __( 'Enables semantic search', 'visualwebs-ml' ),
    261                 'default' => true,
    262                 'on'      => 'Enabled',
    263                 'off'     => 'Disabled',
    264                 'desc'    => __( 'Enables the Semantic search. Requires a vector database connection.', 'visualwebs-ml' ),
    265             ),
    266             array(
    267                 'id'       => 'vector_database_service',
    268                 'type'     => 'text',
    269                 'title'    => __( 'Vector database service', 'visualwebs-ml' ),
    270                 'required' => array( 'use_customer_api_keys', '=', '1' ),
    271                 'desc'     => __( 'Use a free Pinacone account as starting point. Note you would need a paid subscription for large websites.', 'visualwebs-ml' ),
    272             ),
    273             array(
    274                 'id'       => 'vector_database_api_key',
    275                 'type'     => 'text',
    276                 'title'    => __( 'Vector database Api key', 'visualwebs-ml' ),
    277                 'required' => array( 'use_customer_api_keys', '=', '1' ),
    278             ),
    279             array(
    280                 'id'       => 'vector_database_index_host',
    281                 'type'     => 'text',
    282                 'title'    => __( 'Vector database index host', 'visualwebs-ml' ),
    283                 'required' => array( 'use_customer_api_keys', '=', '1' ),
    284                 'desc'     => __( 'Find it in Pinecone panel, select the desired index and copy the host.', 'visualwebs-ml' ),
    285             ),
    286             array(
    287                 'id'       => 'vector_index_namespace',
    288                 'type'     => 'text',
    289                 'title'    => __( 'Vector index namespace', 'visualwebs-ml' ),
    290                 'required' => array( 'use_customer_api_keys', '=', '1' ),
    291                 'desc'     => __( 'Optional for paid vector accounts. Leave empty as general rule.', 'visualwebs-ml' ),
    292             ),
    293             array(
    294                 'id'      => 'vector_min_search_score',
    295                 'type'    => 'text',
    296                 'title'   => __( 'Vector min search score', 'visualwebs-ml' ),
    297                 'default' => '0.75',
    298                 'desc'    => __( 'Start using 0.75. Use lower values to use vector data more frequently.', 'visualwebs-ml' ),
    299             ),
    300             array(
    301                 'id'      => 'vector_store_dynamic_data',
    302                 'type'    => 'switch',
    303                 'title'   => __( 'Store Product Price and Stock Data', 'visualwebs-ml' ),
    304                 'default' => false,
    305                 'on'      => 'Enabled',
    306                 'off'     => 'Disabled',
    307                 'desc'    => __( 'Enable if your catalog doesn\'t change frequently. This will add product price and stock data to chatbot knowledge.', 'visualwebs-ml' ),
     187                'id'      => 'admin_enabled',
     188                'type'    => 'switch',
     189                'title'   => __( 'Enable admin widget', 'visualwebs-ml' ),
     190                'default' => false,
     191                'on'      => 'Enabled',
     192                'off'     => 'Disabled',
     193                'desc'    => __( 'Enable chatbot widget on admin screens.', 'visualwebs-ml' ),
    308194            ),
    309195        ),
     
    313199// Dynamic Pricing Group.
    314200Redux::setSection(
    315     $opt_name,
     201    $visualwebs_ml_opt_name,
    316202    array(
    317203        'title'  => __( 'SmartPricing AI', 'visualwebs-ml' ),
     
    344230                'desc'    => __( 'Automatically replaces catalog prices with daily AI predictions.', 'visualwebs-ml' ),
    345231            ),
    346             array(
    347                 'id'      => 'dynamic_pricing_min_profit',
    348                 'type'    => 'text',
    349                 'title'   => __( 'Minimum Profit (%)', 'visualwebs-ml' ),
    350                 'default' => '10',
    351                 'desc'    => __( 'Minimum profit margin as a percentage.', 'visualwebs-ml' ),
    352             ),
    353             array(
    354                 'id'      => 'dynamic_pricing_max_profit',
    355                 'type'    => 'text',
    356                 'title'   => __( 'Maximum Profit (%)', 'visualwebs-ml' ),
    357                 'default' => '35',
    358                 'desc'    => __( 'Maximum profit margin as a percentage.', 'visualwebs-ml' ),
    359             ),
    360         ),
    361     )
    362 );
     232        ),
     233    )
     234);
  • visualwebs-ml/tags/5.5.0/readme.txt

    r3357523 r3476789  
    33Tags: AI, Chatbot, Machine Learning, Spam, ChatGPT
    44Requires at least: 6.2 
    5 Tested up to: 6.8 
     5Tested up to: 6.9 
    66Requires PHP: 7.4 
    7 Stable tag: 5.4.3
     7Stable tag: 5.5.0
    88License: GPLv2 or later 
    99License URI: https://visualwebs.eu/terms-and-conditions/ 
     
    8080
    8181== Changelog ==
    82 = 5.2.0 =
    83 * Initial release.
    84 = 5.3.0 =
    85 * Removed the minimum data requirement for Spam and Sentiment Analysis widgets. These can now display results even with just one review, without needing to train a model.
     82= 5.5.0 =
     83* Major Update: Migrated to SaaS architecture (matches Magento module functionality)
     84* New: n8n workflow integration with event system (order events, customer events, product events)
     85* New: Product feed generator with hourly cron job for semantic search sync
     86* New: Page feed generator with daily cron job
     87* New: Sales feed generator for SmartPricing AI training data
     88* New: Insights feed generator (`insights_feed.json`) for dashboard widgets
     89* New: Manual feed generation buttons in admin panel
     90* New: Chatbot admin/backend integration with secure admin endpoint (capability + nonce)
     91* Security: GDPR-compliant payload anonymization for workflows
     92* Improved: Simplified admin interface (dashboard + settings + feed generation)
     93* Deprecated: Database table access methods (use SaaS API instead)
     94* Deprecated: Semantic search and dynamic pricing queue grid classes (use SaaS dashboard)
     95* Performance: Removed legacy cron jobs and unused local processing paths
     96
     97= 5.4.3 =
     98* Added English/Spanish translation support for widgets and dashboard.
     99= 5.4.2 =
     100* Improved: Simplified chatbot security to ensure compatibility with caching plugins and language translators. Security checks are now handled by our remote service, which uses a license-based rate limiter for protection.
     101= 5.4.1 =
     102* New feature: Track SmartPricing AI training results together with prediction jobs for improved monitoring and transparency.
    86103= 5.4.0 =
    87104* New feature: AI-powered dynamic pricing to help you set the best product sale price and maximize WooCommerce profits.
    88105* New feature: Bulk append products, categories, or pages for faster AI processing and management.
    89106* New feature: Attach PDF, DOC, or text files to each semantic search item. Uploaded files are added to the chatbot's knowledge base.
    90 = 5.4.1 =
    91 * New feature: Track SmartPricing AI training results together with prediction jobs for improved monitoring and transparency.
    92 = 5.4.2 =
    93 * Improved: Simplified chatbot security to ensure compatibility with caching plugins and language translators. Security checks are now handled by our remote service, which uses a license-based rate limiter for protection.
    94 = 5.4.3 =
    95 * Added English/Spanish translation support for widgets and dashboard.
     107= 5.3.0 =
     108* Removed the minimum data requirement for Spam and Sentiment Analysis widgets. These can now display results even with just one review, without needing to train a model.
     109= 5.2.0 =
     110* Initial release.
    96111
    97112== License ==
  • visualwebs-ml/tags/5.5.0/templates/ml-dashboard.php

    r3319473 r3476789  
    99 * @copyright Visualwebs Spain
    1010 * @license See https://visualwebs.eu/terms-and-conditions/ for license details.
     11 *
     12 * @since 5.5.0 - Migrated to SaaS architecture
    1113 */
    1214
     
    1921}
    2022
    21 use Visualwebs\ML\Block\Adminhtml\Dashboard;
    2223use Visualwebs\ML\Helper\Data as Helper;
    2324
    24 $dashboard = new Dashboard();
     25$visualwebs_ml_helper = new Helper();
    2526
    26 if ( ! $dashboard->getIsVisualwebsMlServicesEnabled() ) {
     27if ( ! $visualwebs_ml_helper->getIsVisualwebsMlServicesEnabled() ) {
    2728    echo '<div class="notice notice-warning"><p>';
    2829    echo esc_html__( 'Please purchase a subscription ', 'visualwebs-ml' );
     
    3536}
    3637
    37 if ( isset( $_GET['clear_cache'] ) ) {
    38     check_admin_referer( 'clear_cache_action' );
    39     if ( true === (bool) $_GET['clear_cache'] ) {
    40         delete_transient( Helper::CACHE_TRANSIENT );
    41     }
    42 }
     38$visualwebs_ml_dashboard_url = 'https://saas.visualwebs.eu/dashboard';
     39$visualwebs_ml_api_key = $visualwebs_ml_helper->getApiKey();
     40$visualwebs_ml_store_id = $visualwebs_ml_helper->getStoreId();
     41$visualwebs_ml_workflows_enabled = $visualwebs_ml_helper->getConfig('enable_workflows', false);
     42?>
     43
     44<div class="wrap">
     45    <h1><?php esc_html_e('Visualwebs AI Cloud Suite', 'visualwebs-ml'); ?></h1>
     46   
     47    <div class="card">
     48        <h2><?php esc_html_e('SaaS Dashboard', 'visualwebs-ml'); ?></h2>
     49        <p><?php esc_html_e('All AI features are managed from the centralized SaaS dashboard:', 'visualwebs-ml'); ?></p>
     50        <ul>
     51            <li><?php esc_html_e('Semantic Search Queue Management', 'visualwebs-ml'); ?></li>
     52            <li><?php esc_html_e('SmartPricing AI Jobs & Training', 'visualwebs-ml'); ?></li>
     53            <li><?php esc_html_e('n8n Workflow Monitoring', 'visualwebs-ml'); ?></li>
     54            <li><?php esc_html_e('Analytics & Insights', 'visualwebs-ml'); ?></li>
     55        </ul>
     56        <p>
     57            <a href="<?php echo esc_url($visualwebs_ml_dashboard_url); ?>" target="_blank" class="button button-primary button-hero">
     58                <?php esc_html_e('Open SaaS Dashboard', 'visualwebs-ml'); ?> →
     59            </a>
     60        </p>
     61    </div>
     62   
     63    <div class="card">
     64        <h2><?php esc_html_e('Configuration Status', 'visualwebs-ml'); ?></h2>
     65        <table class="widefat">
     66            <tbody>
     67                <tr>
     68                    <td><strong><?php esc_html_e('API Key', 'visualwebs-ml'); ?></strong></td>
     69                    <td>
     70                        <?php if ($visualwebs_ml_api_key): ?>
     71                            <span style="color: #46b450;">✓ <?php esc_html_e('Configured', 'visualwebs-ml'); ?></span>
     72                        <?php else: ?>
     73                            <span style="color: #dc3232;">✗ <?php esc_html_e('Missing', 'visualwebs-ml'); ?></span>
     74                        <?php endif; ?>
     75                    </td>
     76                </tr>
     77                <tr>
     78                    <td><strong><?php esc_html_e('Store ID', 'visualwebs-ml'); ?></strong></td>
     79                    <td>
     80                        <?php if ($visualwebs_ml_store_id): ?>
     81                            <span style="color: #46b450;">✓ <?php echo esc_html($visualwebs_ml_store_id); ?></span>
     82                        <?php else: ?>
     83                            <span style="color: #dc3232;">✗ <?php esc_html_e('Missing', 'visualwebs-ml'); ?></span>
     84                        <?php endif; ?>
     85                    </td>
     86                </tr>
     87                <tr>
     88                    <td><strong><?php esc_html_e('n8n Workflows', 'visualwebs-ml'); ?></strong></td>
     89                    <td>
     90                        <?php if ($visualwebs_ml_workflows_enabled): ?>
     91                            <span style="color: #46b450;">✓ <?php esc_html_e('Enabled', 'visualwebs-ml'); ?></span>
     92                        <?php else: ?>
     93                            <span style="color: #999;">✗ <?php esc_html_e('Disabled', 'visualwebs-ml'); ?></span>
     94                        <?php endif; ?>
     95                    </td>
     96                </tr>
     97                <tr>
     98                    <td><strong><?php esc_html_e('SmartPricing AI', 'visualwebs-ml'); ?></strong></td>
     99                    <td>
     100                        <?php if ($visualwebs_ml_helper->getIsDynamicPricingEnabled()): ?>
     101                            <span style="color: #46b450;">✓ <?php esc_html_e('Enabled', 'visualwebs-ml'); ?></span>
     102                        <?php else: ?>
     103                            <span style="color: #999;">✗ <?php esc_html_e('Disabled', 'visualwebs-ml'); ?></span>
     104                        <?php endif; ?>
     105                    </td>
     106                </tr>
     107                <tr>
     108                    <td><strong><?php esc_html_e('Chatbot Widget', 'visualwebs-ml'); ?></strong></td>
     109                    <td>
     110                        <?php if ($visualwebs_ml_helper->getConfig('chatbot_enabled', false)): ?>
     111                            <span style="color: #46b450;">✓ <?php esc_html_e('Enabled', 'visualwebs-ml'); ?></span>
     112                        <?php else: ?>
     113                            <span style="color: #999;">✗ <?php esc_html_e('Disabled', 'visualwebs-ml'); ?></span>
     114                        <?php endif; ?>
     115                    </td>
     116                </tr>
     117            </tbody>
     118        </table>
     119    </div>
     120
     121    <div class="card">
     122        <h2><?php esc_html_e('Feed Synchronization', 'visualwebs-ml'); ?></h2>
     123        <p><?php esc_html_e('Automatic feed generation is configured:', 'visualwebs-ml'); ?></p>
     124        <ul>
     125            <li><strong><?php esc_html_e('Product Feed', 'visualwebs-ml'); ?>:</strong> <?php esc_html_e('Hourly', 'visualwebs-ml'); ?></li>
     126            <li><strong><?php esc_html_e('Page Feed', 'visualwebs-ml'); ?>:</strong> <?php esc_html_e('Daily at 2 AM', 'visualwebs-ml'); ?></li>
     127            <li><strong><?php esc_html_e('Sales Feed', 'visualwebs-ml'); ?>:</strong> <?php esc_html_e('Daily at 2 AM', 'visualwebs-ml'); ?></li>
     128        </ul>
     129        <p>
     130            <a href="<?php echo esc_url(admin_url('admin.php?page=visualwebs-ml-feeds')); ?>" class="button">
     131                <?php esc_html_e('Manage Feeds', 'visualwebs-ml'); ?>
     132            </a>
     133        </p>
     134    </div>
     135
     136    <div class="card">
     137        <h2><?php esc_html_e('Quick Links', 'visualwebs-ml'); ?></h2>
     138        <p>
     139            <a href="<?php echo esc_url(admin_url('admin.php?page=visualwebs-ml-manage-options')); ?>" class="button">
     140                <?php esc_html_e('Settings', 'visualwebs-ml'); ?>
     141            </a>
     142            <a href="<?php echo esc_url(admin_url('admin.php?page=visualwebs-ml-feeds')); ?>" class="button">
     143                <?php esc_html_e('Feed Generation', 'visualwebs-ml'); ?>
     144            </a>
     145            <a href="https://visualwebs.eu/docs/plugin/ai-cloud-suite/" target="_blank" class="button">
     146                <?php esc_html_e('Documentation', 'visualwebs-ml'); ?>
     147            </a>
     148        </p>
     149    </div>
     150</div>
  • visualwebs-ml/tags/5.5.0/vendor/autoload.php

    r3303071 r3476789  
    1515        }
    1616    }
    17     trigger_error(
    18         $err,
    19         E_USER_ERROR
    20     );
     17    throw new RuntimeException($err);
    2118}
    2219
  • visualwebs-ml/tags/5.5.0/vendor/composer/autoload_static.php

    r3319473 r3476789  
    1212
    1313    public static $prefixLengthsPsr4 = array (
    14         'V' => 
     14        'V' =>
    1515        array (
    1616            'Visualwebs\\ML\\' => 14,
    1717        ),
    18         'S' => 
     18        'S' =>
    1919        array (
    2020            'Symfony\\Polyfill\\Mbstring\\' => 26,
    2121        ),
    22         'P' => 
     22        'P' =>
    2323        array (
    2424            'PhpOffice\\PhpWord\\' => 18,
     
    2828
    2929    public static $prefixDirsPsr4 = array (
    30         'Visualwebs\\ML\\' => 
     30        'Visualwebs\\ML\\' =>
    3131        array (
    3232            0 => __DIR__ . '/..' . '/visualwebs-ml',
    3333        ),
    34         'Symfony\\Polyfill\\Mbstring\\' => 
     34        'Symfony\\Polyfill\\Mbstring\\' =>
    3535        array (
    3636            0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
    3737        ),
    38         'PhpOffice\\PhpWord\\' => 
     38        'PhpOffice\\PhpWord\\' =>
    3939        array (
    4040            0 => __DIR__ . '/..' . '/phpoffice/phpword/src/PhpWord',
    4141        ),
    42         'PhpOffice\\Math\\' => 
     42        'PhpOffice\\Math\\' =>
    4343        array (
    4444            0 => __DIR__ . '/..' . '/phpoffice/math/src/Math',
     
    4747
    4848    public static $prefixesPsr0 = array (
    49         'S' => 
     49        'S' =>
    5050        array (
    51             'Smalot\\PdfParser\\' => 
     51            'Smalot\\PdfParser\\' =>
    5252            array (
    5353                0 => __DIR__ . '/..' . '/smalot/pdfparser/src',
  • visualwebs-ml/tags/5.5.0/vendor/composer/platform_check.php

    r3319484 r3476789  
    2020        }
    2121    }
    22     trigger_error(
    23         'Composer detected issues in your platform: ' . implode(' ', $issues),
    24         E_USER_ERROR
     22    throw new \RuntimeException(
     23        'Composer detected issues in your platform: ' . implode(' ', $issues)
    2524    );
    2625}
  • visualwebs-ml/tags/5.5.0/vendor/visualwebs-ml/Activator.php

    r3325281 r3476789  
    33namespace Visualwebs\ML;
    44
     5/**
     6 * Plugin Activator
     7 *
     8 * @package Visualwebs\ML
     9 * @since 5.5.0
     10 *
     11 * Note: Database tables removed in v5.5.0 - SaaS architecture migration.
     12 * All data is now managed centrally by the SaaS platform.
     13 * Configuration is stored in wp_options table.
     14 */
    515class Activator
    616{
     17    /**
     18     * Plugin activation hook
     19     *
     20     * @since 5.5.0 No database tables created - SaaS manages all data
     21     */
    722    public static function activate()
    823    {
    9         global $wpdb;
    10 
    11         $sematic_queue_table_name = $wpdb->prefix . 'visualwebs_ml_semantic_queue';
    12         $charset_collate = $wpdb->get_charset_collate();
    13 
    14         $sql1 = "CREATE TABLE $sematic_queue_table_name (
    15             job_id int(11) NOT NULL AUTO_INCREMENT COMMENT 'Job Id',
    16             job_object_entity_id int(11) NOT NULL COMMENT 'Job Object Entity Id',
    17             job_object_type varchar(50) NOT NULL COMMENT 'Job Object Type',
    18             job_object_name varchar(255) DEFAULT NULL COMMENT 'Job Object Name',
    19             job_object_ref varchar(255) DEFAULT NULL COMMENT 'Job Object Ref',
    20             job_object_content text COMMENT 'Job Object Content',
    21             job_object_content_lock smallint(6) NOT NULL DEFAULT '0' COMMENT 'Job Object Content Lock',
    22             job_status smallint(6) NOT NULL DEFAULT '0' COMMENT 'Status',
    23             job_sync_status smallint(6) NOT NULL DEFAULT '0' COMMENT 'Sync Status',
    24             created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Date and time of job creation',
    25             updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Date and time of job update',
    26             job_errors varchar(255) DEFAULT NULL COMMENT 'Job Errors',
    27             job_object_files_content text COMMENT 'Job Object Files Content',
    28             PRIMARY KEY  (job_id),
    29             UNIQUE KEY ML_SEMANTIC_QUEUE_JOB_OBJECT_TYPE_JOB_OBJECT_ENTITY_ID (job_object_type, job_object_entity_id)
    30         ) $charset_collate;";
    31 
    32         $dynamic_pricing_jobs_table_name = $wpdb->prefix . 'visualwebs_ml_dynamic_pricing_jobs';
    33 
    34         $sql2 = "CREATE TABLE $dynamic_pricing_jobs_table_name (
    35             job_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    36             job_hash VARCHAR(128) NOT NULL,
    37             job_type VARCHAR(32) NOT NULL DEFAULT 'train',
    38             job_status VARCHAR(20) NOT NULL DEFAULT 'pending',
    39             created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Date and time of job creation',
    40             updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Date and time of job update',
    41             job_data LONGTEXT,
    42             job_response LONGTEXT,
    43             PRIMARY KEY  (job_id),
    44             UNIQUE KEY job_hash (job_hash)
    45         ) $charset_collate;";
    46 
    47         require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    48         dbDelta($sql1);
    49         dbDelta($sql2);
     24        // No database tables needed - SaaS architecture
     25        // Configuration is stored in wp_options via Redux Framework
     26        // Feeds are generated as JSON files in wp-content/uploads/visualwebs-ai/
     27       
     28        // Ensure upload directory exists
     29        $upload_dir = wp_upload_dir();
     30        $visualwebs_dir = $upload_dir['basedir'] . '/visualwebs-ai';
     31       
     32        if (!file_exists($visualwebs_dir)) {
     33            wp_mkdir_p($visualwebs_dir);
     34        }
     35       
     36        // Set default options if not present
     37        $options = get_option('visualwebs_ml_options');
     38       
     39        if ($options === false) {
     40            $default_options = array(
     41                'enabled' => false,
     42                'api_enabled' => false,
     43                'chatbot_enabled' => false,
     44                'enable_workflows' => false,
     45                'anonymize_payload' => false,
     46            );
     47           
     48            add_option('visualwebs_ml_options', $default_options);
     49        }
     50       
     51        // Flush rewrite rules for REST API endpoints
     52        flush_rewrite_rules();
    5053    }
    5154}
  • visualwebs-ml/tags/5.5.0/vendor/visualwebs-ml/Admin.php

    r3319473 r3476789  
    2525    {
    2626        add_action('admin_menu', [__CLASS__, 'add_admin_menu']);
    27         add_action('admin_footer', [__CLASS__, 'widget_page']);
     27        add_action('admin_footer', [__CLASS__, 'render_chatbot_widget']);
    2828        add_action('wp_ajax_visualwebs_ml_search_entity', [__CLASS__, 'visualwebs_ml_search_entity']);
    2929        if (self::get_helper()->getIsDynamicPricingEnabled()) {
     
    6262        add_submenu_page(
    6363            'visualwebs-ml',
    64             'Semantic Search',
    65             'Semantic Search',
    66             'manage_options',
    67             'visualwebs-ml-semantic-queue',
    68             [__CLASS__, 'semantic_search_queue_page']
    69         );
    70 
    71         add_submenu_page(
    72             null,
    73             'Add New Search Item',
    74             'Add New Search Item',
    75             'manage_options',
    76             'visualwebs-ml-semantic-add',
    77             [__CLASS__, 'semantic_search_add_page']
    78         );
    79 
    80         add_submenu_page(
    81             null,
    82             'Edit Search Item',
    83             'Edit Search Item',
    84             'manage_options',
    85             'visualwebs-ml-semantic-edit',
    86             [__CLASS__, 'semantic_search_edit_page']
    87         );
    88 
    89         add_submenu_page(
    90             'visualwebs-ml',
    91             'SmartPricing AI',
    92             'SmartPricing AI',
    93             'manage_options',
    94             'visualwebs-ml-dynamic-pricing-queue',
    95             [__CLASS__, 'dynamic_pricing_queue_page']
    96         );
    97 
    98         add_submenu_page(
    99             null,
    100             'View Job Item',
    101             'View Job Item',
    102             'manage_options',
    103             'visualwebs-ml-dynamic-pricing-view',
    104             [__CLASS__, 'dynamic_pricing_queue_view_page']
    105         );
    106 
    107         add_submenu_page(
    108             null,
    109             'Train',
    110             'Train',
    111             'manage_options',
    112             'visualwebs-ml-dynamic-pricing-train',
    113             [__CLASS__, 'dynamic_pricing_train_page']
    114         );
    115 
    116         add_submenu_page(
    117             null,
    118             'Predict',
    119             'Predict',
    120             'manage_options',
    121             'visualwebs-ml-dynamic-pricing-predict',
    122             [__CLASS__, 'dynamic_pricing_predict_page']
    123         );
    124 
    125         add_submenu_page(
    126             'visualwebs-ml',
    12764            'Settings',
    12865            'Settings',
     
    13067            'visualwebs-ml-manage-options',
    13168            [__CLASS__, 'settings_page']
     69        );
     70
     71        add_submenu_page(
     72            'visualwebs-ml',
     73            'Feed Generation',
     74            'Feed Generation',
     75            'manage_options',
     76            'visualwebs-ml-feeds',
     77            [__CLASS__, 'feeds_page']
    13278        );
    13379
     
    264210    public static function dashboard_page()
    265211    {
    266         include(plugin_dir_path(__FILE__) . '../../templates/ml-widgets.php');
    267212        include(plugin_dir_path(__FILE__) . '../../templates/ml-dashboard.php');
    268213    }
    269214
    270     public static function widget_page()
    271     {
    272         include(plugin_dir_path(__FILE__) . '../../templates/ml-widgets.php');
    273     }
    274 
    275     public static function semantic_search_queue_page()
    276     {
    277         include plugin_dir_path(__FILE__) . '../../templates/semantic-search-queue.php';
    278     }
    279 
    280     public static function semantic_search_add_page()
    281     {
    282         include plugin_dir_path(__FILE__) . '../../templates/semantic-search-add.php';
    283     }
    284 
    285     public static function semantic_search_edit_page()
    286     {
    287         include plugin_dir_path(__FILE__) . '../../templates/semantic-search-edit.php';
    288     }
    289 
    290     public static function dynamic_pricing_queue_page()
    291     {
    292         include plugin_dir_path(__FILE__) . '../../templates/dynamic-pricing-queue.php';
    293     }
    294 
    295     public static function dynamic_pricing_queue_view_page()
    296     {
    297         include plugin_dir_path(__FILE__) . '../../templates/dynamic-pricing-view.php';
    298     }
    299 
    300     public static function dynamic_pricing_train_page()
    301     {
    302         include plugin_dir_path(__FILE__) . '../../templates/dynamic-pricing-train.php';
    303     }
    304 
    305     public static function dynamic_pricing_predict_page()
    306     {
    307         include plugin_dir_path(__FILE__) . '../../templates/dynamic-pricing-predict.php';
     215    /**
     216     * Render chatbot widget in admin footer.
     217     *
     218     * @since 5.5.0
     219     */
     220    public static function render_chatbot_widget()
     221    {
     222        require plugin_dir_path(__FILE__) . '../../templates/n8n-chatbot-widget.php';
     223    }
     224
     225    public static function settings_page()
     226    {
     227        // Redux Framework handles this page
     228    }
     229
     230    public static function feeds_page()
     231    {
     232        require_once plugin_dir_path(__FILE__) . '../../includes/feeds/class-product-feed-generator.php';
     233        require_once plugin_dir_path(__FILE__) . '../../includes/feeds/class-page-feed-generator.php';
     234        require_once plugin_dir_path(__FILE__) . '../../includes/feeds/class-sales-feed-generator.php';
     235        require_once plugin_dir_path(__FILE__) . '../../includes/feeds/class-insights-feed-generator.php';
     236
     237        $message = '';
     238        $messageType = '';
     239
     240        if (isset($_POST['generate_products']) && check_admin_referer('visualwebs_ml_feeds', 'visualwebs_ml_feeds_nonce')) {
     241            $generator = new \VisualwebsML_ProductFeedGenerator();
     242            $url = $generator->generate();
     243            if ($url) {
     244                $message = sprintf(__('Product feed generated successfully: %s', 'visualwebs-ml'), $url);
     245                $messageType = 'success';
     246            } else {
     247                $message = __('Failed to generate product feed', 'visualwebs-ml');
     248                $messageType = 'error';
     249            }
     250        }
     251
     252        if (isset($_POST['generate_pages']) && check_admin_referer('visualwebs_ml_feeds', 'visualwebs_ml_feeds_nonce')) {
     253            $generator = new \VisualwebsML_PageFeedGenerator();
     254            $url = $generator->generate();
     255            if ($url) {
     256                $message = sprintf(__('Page feed generated successfully: %s', 'visualwebs-ml'), $url);
     257                $messageType = 'success';
     258            } else {
     259                $message = __('Failed to generate page feed', 'visualwebs-ml');
     260                $messageType = 'error';
     261            }
     262        }
     263
     264        if (isset($_POST['generate_sales']) && check_admin_referer('visualwebs_ml_feeds', 'visualwebs_ml_feeds_nonce')) {
     265            $generator = new \VisualwebsML_SalesFeedGenerator();
     266            $url = $generator->generate();
     267            if ($url) {
     268                $message = sprintf(__('Sales feed generated successfully: %s', 'visualwebs-ml'), $url);
     269                $messageType = 'success';
     270            } else {
     271                $message = __('Failed to generate sales feed', 'visualwebs-ml');
     272                $messageType = 'error';
     273            }
     274        }
     275
     276        if (isset($_POST['generate_insights']) && check_admin_referer('visualwebs_ml_feeds', 'visualwebs_ml_feeds_nonce')) {
     277            $generator = new \VisualwebsML_InsightsFeedGenerator();
     278            $url = $generator->generate();
     279            if ($url) {
     280                $message = sprintf(__('Insights feed generated successfully: %s', 'visualwebs-ml'), $url);
     281                $messageType = 'success';
     282            } else {
     283                $message = __('Failed to generate insights feed', 'visualwebs-ml');
     284                $messageType = 'error';
     285            }
     286        }
     287
     288        ?>
     289        <div class="wrap">
     290            <h1><?php esc_html_e('Feed Generation', 'visualwebs-ml'); ?></h1>
     291           
     292            <?php if ($message): ?>
     293                <div class="notice notice-<?php echo esc_attr($messageType); ?> is-dismissible">
     294                    <p><?php echo esc_html($message); ?></p>
     295                </div>
     296            <?php endif; ?>
     297
     298            <p><?php esc_html_e('Manually trigger feed generation. Feeds are also generated automatically via cron jobs.', 'visualwebs-ml'); ?></p>
     299
     300            <div class="card">
     301                <h2><?php esc_html_e('Product Feed', 'visualwebs-ml'); ?></h2>
     302                <p><?php esc_html_e('Generates JSON feed of all products for semantic search synchronization. Auto-generated hourly.', 'visualwebs-ml'); ?></p>
     303                <form method="post">
     304                    <?php wp_nonce_field('visualwebs_ml_feeds', 'visualwebs_ml_feeds_nonce'); ?>
     305                    <button type="submit" name="generate_products" class="button button-primary">
     306                        <?php esc_html_e('Generate Product Feed', 'visualwebs-ml'); ?>
     307                    </button>
     308                </form>
     309            </div>
     310
     311            <div class="card">
     312                <h2><?php esc_html_e('Page Feed', 'visualwebs-ml'); ?></h2>
     313                <p><?php esc_html_e('Generates JSON feed of all pages and posts for content search. Auto-generated daily at 2 AM.', 'visualwebs-ml'); ?></p>
     314                <form method="post">
     315                    <?php wp_nonce_field('visualwebs_ml_feeds', 'visualwebs_ml_feeds_nonce'); ?>
     316                    <button type="submit" name="generate_pages" class="button button-primary">
     317                        <?php esc_html_e('Generate Page Feed', 'visualwebs-ml'); ?>
     318                    </button>
     319                </form>
     320            </div>
     321
     322            <div class="card">
     323                <h2><?php esc_html_e('Sales Feed', 'visualwebs-ml'); ?></h2>
     324                <p><?php esc_html_e('Generates JSON feed of sales data for SmartPricing AI training. Auto-generated daily at 2 AM.', 'visualwebs-ml'); ?></p>
     325                <form method="post">
     326                    <?php wp_nonce_field('visualwebs_ml_feeds', 'visualwebs_ml_feeds_nonce'); ?>
     327                    <button type="submit" name="generate_sales" class="button button-primary">
     328                        <?php esc_html_e('Generate Sales Feed', 'visualwebs-ml'); ?>
     329                    </button>
     330                </form>
     331            </div>
     332
     333            <div class="card">
     334                <h2><?php esc_html_e('Insights Feed', 'visualwebs-ml'); ?></h2>
     335                <p><?php esc_html_e('Generates JSON feed of dashboard insights (sales, bestsellers, reviews, registrations) for SaaS widgets. Auto-generated daily at 3 AM.', 'visualwebs-ml'); ?></p>
     336                <form method="post">
     337                    <?php wp_nonce_field('visualwebs_ml_feeds', 'visualwebs_ml_feeds_nonce'); ?>
     338                    <button type="submit" name="generate_insights" class="button button-primary">
     339                        <?php esc_html_e('Generate Insights Feed', 'visualwebs-ml'); ?>
     340                    </button>
     341                </form>
     342            </div>
     343
     344            <div class="card">
     345                <h2><?php esc_html_e('Feed Files', 'visualwebs-ml'); ?></h2>
     346                <p><?php esc_html_e('Generated feeds are stored in:', 'visualwebs-ml'); ?> <code><?php echo esc_html(wp_upload_dir()['baseurl'] . '/visualwebs-ml/'); ?></code></p>
     347                <ul>
     348                    <li><strong>product_feed.json</strong> - Product catalog</li>
     349                    <li><strong>page_feed.json</strong> - Pages and posts</li>
     350                    <li><strong>sales_feed.json</strong> - Sales data for SmartPricing</li>
     351                    <li><strong>insights_feed.json</strong> - Dashboard insights and statistics</li>
     352                </ul>
     353            </div>
     354        </div>
     355        <?php
    308356    }
    309357
  • visualwebs-ml/tags/5.5.0/vendor/visualwebs-ml/Block/Adminhtml/Dashboard.php

    r3337157 r3476789  
    320320     * Retrieve the existing widgets for the dashboard.
    321321     *
    322      * @return array An array of user widgets.
     322     * @return array An array of user widgets (empty - config removed).
    323323     */
    324324    public function getUserWidgets()
    325325    {
    326         return $this->helper->getUserWidgets();
     326        // User widgets config removed (now managed in SaaS)
     327        return [];
    327328    }
    328329
  • visualwebs-ml/tags/5.5.0/vendor/visualwebs-ml/Block/ChatWidget.php

    r3337157 r3476789  
    4040
    4141    /**
     42     * Retrieve the URL for the admin chatbot proxy API.
     43     *
     44     * @return string
     45     */
     46    public function getAdminChatApiUrl()
     47    {
     48        return $this->helper->getAdminChatApiUrl();
     49    }
     50
     51    /**
    4252     * Check if the chatbot is enabled.
    4353     *
     
    4757    {
    4858        return $this->helper->getIsChatbotEnabled();
     59    }
     60
     61    /**
     62     * Check if the admin chatbot is enabled.
     63     *
     64     * @return bool
     65     */
     66    public function getIsAdminChatbotEnabled()
     67    {
     68        return $this->helper->getIsAdminChatbotEnabled();
    4969    }
    5070
     
    235255        return $this->helper->getCurrentFrontendLanguageShort();
    236256    }
     257
     258    /**
     259     * Get chatbot namespace.
     260     *
     261     * @return string
     262     */
     263    public function getChatbotNamespace()
     264    {
     265        return $this->helper->getChatbotNamespace();
     266    }
     267
     268    /**
     269     * Get chatbot store ID.
     270     *
     271     * @return string
     272     */
     273    public function getChatbotStoreId()
     274    {
     275        return $this->helper->getChatbotStoreId();
     276    }
     277
     278    /**
     279     * Get "Powered by" text.
     280     *
     281     * @return string
     282     */
     283    public function getChatbotPoweredByText()
     284    {
     285        return $this->helper->getChatbotPoweredByText();
     286    }
     287
     288    /**
     289     * Get "Powered by" link.
     290     *
     291     * @return string
     292     */
     293    public function getChatbotPoweredByLink()
     294    {
     295        return $this->helper->getChatbotPoweredByLink();
     296    }
     297
     298    /**
     299     * Get chatbot assets base URL.
     300     *
     301     * @return string
     302     */
     303    public function getChatbotAssetsBaseUrl()
     304    {
     305        return $this->helper->getChatbotAssetsBaseUrl();
     306    }
     307
     308    /**
     309     * Get conversation unique session ID.
     310     *
     311     * @return string
     312     */
     313    public function getConversationUniqueSessionId()
     314    {
     315        return $this->helper->getConversationUniqueSessionId();
     316    }
    237317}
  • visualwebs-ml/tags/5.5.0/vendor/visualwebs-ml/Helper/Chatbot.php

    r3319473 r3476789  
    1010class Chatbot extends \Visualwebs\ML\Helper\Data
    1111{
     12    private const SAAS_API_BASE_URL = 'https://saas.visualwebs.eu/api/v1';
     13    private const SAAS_CHATBOT_CONFIG_PATH = '/chatbot/public/config/';
     14    private const SAAS_CHATBOT_CACHE_TTL = DAY_IN_SECONDS;
     15    private const SAAS_CHATBOT_ASSETS_BASE_URL = 'https://saas.visualwebs.eu/assets/chatbot/v1';
     16
    1217    public const CHATBOT_DEFAULT_HEADER_TITLE = 'Support';
    1318    public const CHATBOT_DEFAULT_OFFLINE_MESSAGE = 'Our support system is OFF now, please try later.';
     
    2328        $isChatbotEnabled = $this->getConfig('chatbot_enabled', false);
    2429        $isVisualwebsMlServicesEnabled = $this->getIsVisualwebsMlServicesEnabled();
    25         return $isChatbotEnabled && $isVisualwebsMlServicesEnabled;
     30        $chatApiUrl = $this->getChatApiUrl();
     31
     32        return $isChatbotEnabled && $isVisualwebsMlServicesEnabled && !empty($chatApiUrl);
     33    }
     34
     35    /**
     36     * Check if the admin chatbot is enabled.
     37     *
     38     * @return bool
     39     */
     40    public function getIsAdminChatbotEnabled()
     41    {
     42        $isAdminEnabled = $this->getConfig('admin_enabled', false);
     43        $isVisualwebsMlServicesEnabled = $this->getIsVisualwebsMlServicesEnabled();
     44        $chatApiUrl = $this->getChatApiUrl();
     45
     46        return $isAdminEnabled && $isVisualwebsMlServicesEnabled && !empty($chatApiUrl);
    2647    }
    2748
     
    117138    public function getChatbotMainBackgroundColor()
    118139    {
    119         $backgroundColor = $this->getConfig('chatbot_main_background_color', true);
    120         return $backgroundColor ?: "#000";
     140        $saasColor = $this->getSaasWidgetConfigValue('color');
     141        return $saasColor ? (string)$saasColor : "#000";
    121142    }
    122143
     
    128149    public function getChatbotDisclaimerUrl()
    129150    {
    130         $disclaimerUrl = $this->getConfig('chatbot_disclaimer_url', '');
    131         return $disclaimerUrl ? get_page_link(get_page_by_path($disclaimerUrl)) : get_page_link(get_page_by_path('about-us'));
     151        $saasDisclaimer = $this->getSaasWidgetConfigValue('disclaimer_url');
     152        return $saasDisclaimer ? (string)$saasDisclaimer : '';
    132153    }
    133154
     
    139160    public function getChatbotContext()
    140161    {
    141         $chatBotContext = $this->getChatbotContextValue();
    142         return $chatBotContext ? trim($chatBotContext) : __('Act as E-commerce expert', 'visualwebs-ml');
    143     }
    144 
    145     /**
    146      * Retrieve the context value for the chatbot in config.
    147      *
    148      * @return mixed The user defined context value for the chatbot.
    149      */
    150     public function getChatbotContextValue()
    151     {
    152         $chatBotContext = $this->getConfig('chatbot_context', '');
    153         return $chatBotContext;
     162        $saasPrompt = $this->getSaasWidgetConfigValue('prompt');
     163        return $saasPrompt ? trim((string)$saasPrompt) : __('Act as E-commerce expert', 'visualwebs-ml');
    154164    }
    155165
     
    161171    public function getChatbotHeaderTitle()
    162172    {
    163         $chatBotHeadTitle = $this->getConfig('chatbot_header_title', '');
     173        $saasTitle = $this->getSaasWidgetConfigValue('title');
    164174        $defaultChatBotHeadTitle = self::CHATBOT_DEFAULT_HEADER_TITLE;
    165         return $chatBotHeadTitle ? trim($chatBotHeadTitle) : $defaultChatBotHeadTitle;
     175        return $saasTitle ? trim((string)$saasTitle) : __($defaultChatBotHeadTitle, 'visualwebs-ml');
    166176    }
    167177
     
    173183    public function getChatbotOfflineMessage()
    174184    {
    175         $chatBotOfflineMessage = $this->getConfig('chatbot_offline_message', '');
     185        $saasOfflineMessage = $this->getSaasWidgetConfigValue('offline_message');
    176186        $defaultChatBotOfflineMessage = self::CHATBOT_DEFAULT_OFFLINE_MESSAGE;
    177         return $chatBotOfflineMessage ? trim($chatBotOfflineMessage) : $defaultChatBotOfflineMessage;
     187        return $saasOfflineMessage ? trim((string)$saasOfflineMessage) : __($defaultChatBotOfflineMessage, 'visualwebs-ml');
    178188    }
    179189
     
    185195    public function getChatbotLogoUrl()
    186196    {
    187         if ($imagePath = $this->getConfig('chatbot_logo')) {
    188             if (isset($imagePath['url'])) {
    189                 return $imagePath['url'];
    190             }
    191         }
    192 
    193         return '';
     197        $saasLogo = $this->getSaasWidgetConfigValue('logo');
     198        return $saasLogo ? (string)$saasLogo : '';
     199    }
     200
     201    /**
     202     * Retrieve SaaS chatbot webhook URL.
     203     *
     204     * @return string
     205     */
     206    public function getChatApiUrl()
     207    {
     208        $webhookUrl = $this->getSaasChatbotConfigValue('n8n_webhook_url');
     209        return $webhookUrl ? esc_url_raw((string) $webhookUrl) : '';
     210    }
     211
     212    /**
     213     * Retrieve admin chatbot proxy endpoint (secured local REST endpoint).
     214     *
     215     * @return string
     216     */
     217    public function getAdminChatApiUrl()
     218    {
     219        $nonce = wp_create_nonce('visualwebs_ml_admin_chat');
     220        return esc_url_raw(rest_url('visualwebs-ml/v1/chat-api/admin-call?_vwml_nonce=' . rawurlencode($nonce)));
    194221    }
    195222
     
    225252    }
    226253
     254    /**
     255     * Returns raw SaaS chatbot config payload (cached).
     256     *
     257     * @return array|null
     258     */
     259    private function getSaasChatbotConfig()
     260    {
     261        $storeId = $this->getStoreId();
     262        if (empty($storeId)) {
     263            return null;
     264        }
     265
     266        $cacheKey = 'vwml_chatbot_config_' . md5($storeId);
     267        $cached = get_transient($cacheKey);
     268        if (is_array($cached)) {
     269            return $cached;
     270        }
     271
     272        $url = self::SAAS_API_BASE_URL . self::SAAS_CHATBOT_CONFIG_PATH . rawurlencode($storeId);
     273        $headers = array('Accept' => 'application/json');
     274        $apiKey = $this->getApiKey();
     275        if (!empty($apiKey)) {
     276            $headers['x-api-key'] = $apiKey;
     277        }
     278
     279        $response = wp_remote_get(
     280            $url,
     281            array(
     282                'headers' => $headers,
     283                'timeout' => 8,
     284            )
     285        );
     286
     287        if (is_wp_error($response)) {
     288            $this->debug('[Visualwebs ML] Failed to fetch SaaS chatbot config: ' . $response->get_error_message());
     289            return null;
     290        }
     291
     292        $status = (int) wp_remote_retrieve_response_code($response);
     293        if ($status !== 200) {
     294            $this->debug('[Visualwebs ML] Failed to fetch SaaS chatbot config. HTTP status: ' . $status);
     295            return null;
     296        }
     297
     298        $body = wp_remote_retrieve_body($response);
     299        $decoded = json_decode($body, true);
     300        if (!is_array($decoded)) {
     301            return null;
     302        }
     303
     304        set_transient($cacheKey, $decoded, self::SAAS_CHATBOT_CACHE_TTL);
     305
     306        return $decoded;
     307    }
     308
     309    /**
     310     * Returns a top-level key from SaaS chatbot config payload.
     311     *
     312     * @param string $key
     313     * @return mixed|null
     314     */
     315    private function getSaasChatbotConfigValue($key)
     316    {
     317        $response = $this->getSaasChatbotConfig();
     318        if (!$response || !isset($response['config']) || !is_array($response['config'])) {
     319            return null;
     320        }
     321
     322        return array_key_exists($key, $response['config']) ? $response['config'][$key] : null;
     323    }
     324
     325    /**
     326     * Returns a widget_config key from SaaS config.
     327     *
     328     * @param string $key
     329     * @return mixed|null
     330     */
     331    private function getSaasWidgetConfigValue($key)
     332    {
     333        $response = $this->getSaasChatbotConfig();
     334        if (!$response || !isset($response['config']) || !is_array($response['config'])) {
     335            return null;
     336        }
     337
     338        $config = $response['config'];
     339        if (!isset($config['widget_config'])) {
     340            return null;
     341        }
     342
     343        $widgetConfig = $config['widget_config'];
     344        if (is_string($widgetConfig)) {
     345            $decoded = json_decode($widgetConfig, true);
     346            $widgetConfig = is_array($decoded) ? $decoded : array();
     347        }
     348
     349        return isset($widgetConfig[$key]) ? $widgetConfig[$key] : null;
     350    }
     351
     352    /**
     353     * Get chatbot namespace (for multi-bot identification).
     354     *
     355     * @return string
     356     */
     357    public function getChatbotNamespace()
     358    {
     359        return $this->getSaasChatbotConfigValue('namespace') ?: 'default';
     360    }
     361
     362    /**
     363     * Get chatbot store ID.
     364     *
     365     * @return string
     366     */
     367    public function getChatbotStoreId()
     368    {
     369        return $this->getStoreId();
     370    }
     371
     372    /**
     373     * Get "Powered by" text for chatbot footer.
     374     *
     375     * @return string
     376     */
     377    public function getChatbotPoweredByText()
     378    {
     379        return $this->getSaasChatbotConfigValue('powered_by_text') ?: 'Powered by Visualwebs AI';
     380    }
     381
     382    /**
     383     * Get "Powered by" link for chatbot footer.
     384     *
     385     * @return string
     386     */
     387    public function getChatbotPoweredByLink()
     388    {
     389        return $this->getSaasChatbotConfigValue('powered_by_link') ?: 'https://visualwebs.eu';
     390    }
     391
     392    /**
     393     * Get chatbot assets base URL (where n8n-chat-widget.js is hosted).
     394     *
     395     * @return string
     396     */
     397    public function getChatbotAssetsBaseUrl()
     398    {
     399        return self::SAAS_CHATBOT_ASSETS_BASE_URL;
     400    }
     401
    227402}
  • visualwebs-ml/tags/5.5.0/vendor/visualwebs-ml/Helper/ChatbotApiCall.php

    r3322994 r3476789  
    1010use Visualwebs\ML\Helper\Visualwebs as ApiHelper;
    1111use Visualwebs\ML\Helper\Chatbot as ChatbotHelper;
    12 use Visualwebs\ML\Helper\OpenAi as OpenAiHelper;
    13 use Visualwebs\ML\Helper\Pinecone as PineconeHelper;
     12use Visualwebs\ML\Helper\Data as DataHelper;
    1413
    1514class ChatbotApiCall
     
    2625
    2726    /**
    28      * @var \Visualwebs\ML\Helper\OpenAiHelper
    29      */
    30     protected $openAiHelper;
    31 
    32     /**
    33      * @var \Visualwebs\ML\Helper\PineconeHelper
    34      */
    35     protected $pineconeHelper;
     27     * @var \Visualwebs\ML\Helper\DataHelper
     28     */
     29    protected $dataHelper;
    3630
    3731    /**
     
    4943        $this->apiHelper = new ApiHelper();
    5044        $this->chatbotHelper = new ChatbotHelper();
    51         $this->openAiHelper = new OpenAiHelper();
    52         $this->pineconeHelper = new PineconeHelper();
     45        $this->dataHelper = new DataHelper();
    5346        $this->debugEnabled = $this->chatbotHelper->getIsDebugChatbotEnabled();
    5447    }
     
    6255     * @return void
    6356     */
    64     public function execute()
    65     {
    66         $frontendBaseUrl = $this->chatbotHelper->getFrontendBaseUrl();
    67         $context =  $this->chatbotHelper->getChatbotContextValue();
    68         $input = json_decode(file_get_contents('php://input'), true);
     57    public function execute($input = null)
     58    {
     59        $input = is_array($input) ? $input : json_decode(file_get_contents('php://input'), true);
     60        if (!is_array($input)) {
     61            $input = array();
     62        }
    6963   
    7064        $message = isset($input['message']) ? sanitize_text_field(wp_unslash($input['message'])) : '';
     
    7468            : [];
    7569
    76         // Important, we retrieve here because in frontend is cached
    77         $uniqueSessionId = $this->chatbotHelper->getConversationUniqueSessionId();
    78         $openAiApiKey = $this->openAiHelper->getApiKey();
    79         $offMessage = $this->chatbotHelper->getChatbotOfflineMessage();
    80         $useCustomerApiKeysEnabled = $this->chatbotHelper->getUseCustomerApiKeysEnabled();
    81         $domainNamespace = $this->chatbotHelper->getDomainNamespace();
    82 
    83         if ($useCustomerApiKeysEnabled) {
    84             $args = [
    85                 'openai_api_key' => $openAiApiKey,
    86                 'session_id' => $uniqueSessionId,
    87                 'is_single_prompt' => !$isChat
    88             ];
    89         } else {
    90             $args = [
    91                 'session_id' => $uniqueSessionId,
    92                 'is_single_prompt' => !$isChat
    93             ];
    94         }
    95 
    96         if ($isChat) {
    97 
    98             $args['instructions'] = $context;
    99             $args['catalog_search_url'] = $frontendBaseUrl . "?s=";
    100             $args['tools'] = ["get_order_status", "get_product_stock"];
    101             $args['tools_endpoint'] = $this->chatbotHelper->getCallbackApiUrl();
    102             $args['current_page_info'] = $currentPageInfo;
    103             if ($offMessage && trim($offMessage)) {
    104                 $args['offline_message'] =  $offMessage;
    105             }
    106 
    107             if ($this->chatbotHelper->getIsSemanticSearchEnabled()) {
    108 
    109                 // We offer Pinecone only at this moment
    110 
    111                 $pineconeApiKey = $this->pineconeHelper->getPineconeApiKey();
    112                 $pineconeHost = $this->pineconeHelper->getPineconeIndexHost();
    113                 $pineconeNamespace = $this->pineconeHelper->getPineconeNamespace();
    114                 $vectorSearchMinScore = $this->chatbotHelper->getVectorSearchMinScore();
    115 
    116                 if ($useCustomerApiKeysEnabled) {
    117                     $args = array_merge($args, [
    118                         'pinecone_api_key' => $pineconeApiKey,
    119                         'pinecone_index_host' =>  $pineconeHost,
    120                         'pinecone_namespace' => $pineconeNamespace,
    121                         'vector_search_min_score' => $vectorSearchMinScore
    122                     ]);
    123                 } else {
    124                     $args = array_merge($args, [
    125                         'pinecone_namespace' => $domainNamespace,
    126                         'vector_search_min_score' => $vectorSearchMinScore
    127                     ]);
    128                 }
    129             }
    130         }
    131 
    132         $this->setArgs($args);
    133 
    134         try {
    135             $success = true;
    136             if ($isChat) {
    137                 $reply = $this->apiHelper->sendChatRequest($message, $this->getArgs());
    138             } else {
    139                 $reply = $this->apiHelper->sendPromptRequest($message, $this->getArgs());
    140             }
    141         } catch (\Exception $e) {
    142             $reply = __('Sorry, a problem happened, please try again.', 'visualwebs-ml');
    143             $success = false;
    144         }
    145 
    146         wp_send_json([
    147             'result' => $reply,
    148             'success' => $success
    149         ]);
     70        if (empty($message)) {
     71            return array(
     72                'result' => '',
     73                'success' => false,
     74                'error' => __('Message is required.', 'visualwebs-ml'),
     75            );
     76        }
     77
     78        $payload = array(
     79            'message' => $message,
     80            'user_message' => $message,
     81            'platform' => 'wordpress-frontend',
     82            'store_id' => $this->dataHelper->getStoreId(),
     83            'params' => array(
     84                'session_id' => $this->chatbotHelper->getConversationUniqueSessionId(),
     85                'is_single_prompt' => !$isChat,
     86                'current_page_info' => $currentPageInfo,
     87                'instructions' => $this->chatbotHelper->getChatbotContextValue(),
     88                'offline_message' => $this->chatbotHelper->getChatbotOfflineMessage(),
     89            ),
     90        );
     91
     92        error_log('[Visualwebs ML][Chatbot][request] ' . wp_json_encode(array(
     93            'endpoint' => $this->chatbotHelper->getChatApiUrl(),
     94            'payload_keys' => array_keys($payload),
     95            'scope' => 'frontend',
     96        )));
     97
     98        $response = $this->apiHelper->sendWebhookRequest(
     99            $this->chatbotHelper->getChatApiUrl(),
     100            $payload,
     101            array('x-api-key' => $this->dataHelper->getApiKey())
     102        );
     103
     104        error_log('[Visualwebs ML][Chatbot][response] ' . wp_json_encode(array(
     105            'scope' => 'frontend',
     106            'success' => empty($response['error']),
     107            'has_result' => isset($response['result']) || isset($response['reply']) || isset($response['message']),
     108        )));
     109
     110        return array(
     111            'result' => isset($response['result'])
     112                ? $response['result']
     113                : (isset($response['reply']) ? $response['reply'] : (isset($response['message']) ? $response['message'] : '')),
     114            'success' => empty($response['error']),
     115            'error' => isset($response['error']) ? $response['error'] : '',
     116        );
     117    }
     118
     119    /**
     120     * Execute admin chatbot call via secure local proxy endpoint.
     121     *
     122     * @param array|null $input
     123     * @return array
     124     */
     125    public function execute_admin($input = null)
     126    {
     127        $input = is_array($input) ? $input : json_decode(file_get_contents('php://input'), true);
     128        if (!is_array($input)) {
     129            $input = array();
     130        }
     131
     132        $message = isset($input['message']) ? sanitize_text_field(wp_unslash($input['message'])) : '';
     133        if (empty($message)) {
     134            return array(
     135                'result' => '',
     136                'success' => false,
     137                'error' => __('Message is required.', 'visualwebs-ml'),
     138            );
     139        }
     140
     141        $payload = array(
     142            'message' => $message,
     143            'user_message' => $message,
     144            'platform' => 'wordpress-admin',
     145            'store_id' => $this->dataHelper->getStoreId(),
     146            'params' => array(
     147                'session_id' => 'admin_' . get_current_user_id() . '_' . wp_generate_uuid4(),
     148                'is_single_prompt' => false,
     149                'is_admin' => true,
     150                'current_page_info' => array(
     151                    'full_action_name' => DataHelper::getFullActionName(),
     152                    'params' => array(),
     153                ),
     154            ),
     155        );
     156
     157        error_log('[Visualwebs ML][Chatbot][request] ' . wp_json_encode(array(
     158            'endpoint' => $this->chatbotHelper->getChatApiUrl(),
     159            'payload_keys' => array_keys($payload),
     160            'scope' => 'admin',
     161            'user_id' => get_current_user_id(),
     162        )));
     163
     164        $response = $this->apiHelper->sendWebhookRequest(
     165            $this->chatbotHelper->getChatApiUrl(),
     166            $payload,
     167            array('x-api-key' => $this->dataHelper->getApiKey())
     168        );
     169
     170        error_log('[Visualwebs ML][Chatbot][response] ' . wp_json_encode(array(
     171            'scope' => 'admin',
     172            'success' => empty($response['error']),
     173            'has_result' => isset($response['result']) || isset($response['reply']) || isset($response['message']),
     174        )));
     175
     176        return array(
     177            'result' => isset($response['result'])
     178                ? $response['result']
     179                : (isset($response['reply']) ? $response['reply'] : (isset($response['message']) ? $response['message'] : '')),
     180            'success' => empty($response['error']),
     181            'error' => isset($response['error']) ? $response['error'] : '',
     182        );
    150183    }
    151184
  • visualwebs-ml/tags/5.5.0/vendor/visualwebs-ml/Helper/Data.php

    r3339941 r3476789  
    3333
    3434    /**
     35     * Retrieve Visualwebs API key.
     36     *
     37     * @return string
     38     */
     39    public function getApiKey()
     40    {
     41        return (string) $this->getConfig('api_key', '');
     42    }
     43
     44    /**
     45     * Retrieve SaaS store UUID.
     46     *
     47     * @return string
     48     */
     49    public function getStoreId()
     50    {
     51        return (string) $this->getConfig('store_id', '');
     52    }
     53
     54    /**
    3555     * Check if the Visualwebs ML module is enabled.
    3656     *
     
    7898        return $isMlServicesEnabled && $isMainModuleEnabled && $mlServicesApiKey;
    7999    }
    80 
    81     /**
    82      * Check if the use of customer API keys is enabled.
    83      *
    84      * This method retrieves the configuration setting that determines whether
    85      * the use of customer OpenAI/PInecone API keys is enabled for the Visualwebs ML services.
    86      *
    87      * @return bool Returns true if the use of customer API keys is enabled, false otherwise.
    88      */
    89     public function getUseCustomerApiKeysEnabled()
    90     {
    91         $isUseCustomerApiKeysEnabled = $this->getConfig('use_customer_api_keys');
    92 
    93         return $isUseCustomerApiKeysEnabled;
    94     }
    95 
    96100    /**
    97101     * Check if Dynamic Pricing is enabled.
     
    123127
    124128    /**
    125      * Get the minimum profit percentage for Dynamic Pricing.
    126      *
    127      * @return float
    128      */
    129     public function getDynamicPricingMinProfit()
    130     {
    131         return (float) $this->getConfig('dynamic_pricing_min_profit', 10);
    132     }
    133 
    134     /**
    135      * Get the maximum profit percentage for Dynamic Pricing.
    136      *
    137      * @return float
    138      */
    139     public function getDynamicPricingMaxProfit()
    140     {
    141         return (float) $this->getConfig('dynamic_pricing_max_profit', 35);
    142     }
    143 
    144     /**
    145129     * Check if dynamic pricing can replace prices (enabled AND replace prices).
    146130     *
     
    150134    {
    151135        return $this->getIsDynamicPricingEnabled() && $this->getIsDynamicPricingReplacePrices();
    152     }
    153 
    154     /**
    155      * Check if storing dynamic data (like product price and stock) is enabled.
    156      *
    157      * @return bool
    158      */
    159     public function getIsStoreDynamicDataEnabled()
    160     {
    161         return (bool) $this->getConfig('vector_store_dynamic_data', false);
    162136    }
    163137
     
    197171        $currencySymbol = get_woocommerce_currency_symbol();
    198172        return $currencySymbol ?: "€";
    199     }
    200 
    201     /**
    202      * Retrieve user widgets configuration.
    203      *
    204      * This method fetches the user widgets configuration value from the store's scope configuration.
    205      *
    206      * @return string JSON encoded string of user widgets configuration.
    207      * Returns an empty JSON array "[]" if no configuration is found.
    208      */
    209 
    210     public function getUserWidgets()
    211     {
    212         $userWidgets = $this->getConfig('user_widgets');
    213         $processedWidgets = [];
    214 
    215         if (!empty($userWidgets['redux_repeater_data'])) {
    216             foreach ($userWidgets['redux_repeater_data'] as $index => $widgetData) {
    217                 $processedWidgets[] = [
    218                     'widget_title' => $userWidgets['widget_title'][$index] ?? '',
    219                     'widget_instructions' => $userWidgets['widget_instructions'][$index] ?? '',
    220                     'widget_render_el' => $userWidgets['widget_render_el'][$index] ?? '',
    221                     'widget_source_el' => $userWidgets['widget_source_el'][$index] ?? '',
    222                 ];
    223             }
    224         }
    225 
    226         return $processedWidgets;
    227173    }
    228174
     
    322268    {
    323269        $content = '';
    324         $isStoreDynamicDataEnabled = $this->getIsStoreDynamicDataEnabled();
     270        // Store dynamic data config was removed (now managed in SaaS feed generation)
     271        $isStoreDynamicDataEnabled = false;
    325272
    326273        if (post_type_exists($postType)) {
  • visualwebs-ml/tags/5.5.0/vendor/visualwebs-ml/Helper/Enqueue.php

    r3337202 r3476789  
    33namespace Visualwebs\ML\Helper;
    44
    5 use Visualwebs\ML\Block\ChatWidget;
    6 use Visualwebs\ML\Block\Adminhtml\Widget;
    7 use Visualwebs\ML\Block\Adminhtml\Dashboard;
    8 use Visualwebs\ML\Helper\Data as Helper;
    9 use Visualwebs\ML\Model\Config\Source\MLModelType;
    10 
    115class Enqueue
    126{
    13     private $widget;
    14     private $dashboard;
    15     private $chatWidget;
    16 
    177    /**
    18      * Constructor to initialize dependencies.
     8     * Constructor.
    199     */
    2010    public function __construct()
    2111    {
    22         $this->widget = new Widget();
    23         $this->dashboard = new Dashboard();
    24         $this->chatWidget = new ChatWidget();
     12        // Lightweight enqueue class for 5.5.0+
    2513    }
    2614
     
    3220    public function enqueue_admin_scripts($hook)
    3321    {
    34         wp_enqueue_style('visualwebs-ml-admin-style', plugin_dir_url(__FILE__) . '../../../assets/css/admin-style.css', [], false);
    35 
    36         if ($this->widget->getIsWidgetEnabled()) {
    37 
    38             $nonce = $this->widget->getAppNonce();
    39             $cache_key = Helper::CACHE_TRANSIENT;
    40             $cached_data = get_transient($cache_key);
    41 
    42             if ($cached_data === false) {
    43                 $cached_data = [
    44                     'salesLiveDataset' => $this->widget->getStoreSalesData(),
    45                     'bestsellerLiveDataset' => $this->widget->getProductBestSellersData(),
    46                     'bestsellerMeta' => $this->widget->getProductBestSellersMetadata(),
    47                     'reviewsLiveDataset' => $this->widget->getProductsReviewsData(),
    48                     'customerRegistrationLiveDataset' => $this->widget->getCustomerRegistrationData(),
    49                     'MLApiUrl' => $this->widget->getMLApiUrl(),
    50                     'chatApiUrl' => $this->widget->getChatApiUrl(),
    51                     'timeseriesModel' => MLModelType::TYPE_TEMPORAL_SERIES_DEFAULT,
    52                     'sentimentModel' => MLModelType::TYPE_TEXT_SENTIMENT,
    53                     'spamModel' => MLModelType::TYPE_SPAM_CLASSIFIER,
    54                     'userWidgets' => $this->widget->getUserWidgets() ?: []
    55                 ];
    56 
    57                 set_transient($cache_key, $cached_data, HOUR_IN_SECONDS);
    58             }
    59 
    60             $dynamic_data = [
    61                 'productId' => $this->widget->getProduct() ? $this->widget->getProduct()->get_id() : '0',
    62                 'productRef' => $this->widget->getProduct() ? $this->widget->getProduct()->get_sku() : 'NA',
    63                 'productLiveDataset' => $this->widget->getProduct() ? $this->widget->getProductData() : [],
    64                 'userLanguage' => $this->widget->getCurrentAdminUserLanguageShort(),
    65                 'nonce' => $nonce
    66             ];
    67 
    68             $translated_titles = [
    69                 'salesPredictorTitle' => esc_html__('Sales Predictor', 'visualwebs-ml'),
    70                 'reviewsSentimentTitle' => esc_html__('Reviews Sentiment', 'visualwebs-ml'),
    71                 'bestsellersPredictorTitle' => esc_html__('Bestsellers Predictor', 'visualwebs-ml'),
    72                 'reviewsSpamTitle' => esc_html__('Reviews Spam', 'visualwebs-ml'),
    73                 'apiUsageTitle' => esc_html__('API usage', 'visualwebs-ml'),
    74                 'chatbotHistoryTitle' => esc_html__('Chatbot history', 'visualwebs-ml'),
    75                 'customerRegisterPredictorTitle' => esc_html__('Customer Register Predictor', 'visualwebs-ml'),
    76                 'productPredictorTitle' => esc_html__('Product Predictor', 'visualwebs-ml'),
    77                 'promptTitle' => esc_html__('Prompt', 'visualwebs-ml'),
    78                 'seoGeneratorTitle' => esc_html__('SEO generator', 'visualwebs-ml'),
    79                 'seoGeneratorDescriptionTitle' => esc_html__('SEO generator - Description', 'visualwebs-ml'),
    80                 'seoGeneratorShortDescriptionTitle' => esc_html__('SEO generator - Short description', 'visualwebs-ml')
    81             ];
    82 
    83             $final_data = array_merge($cached_data, $dynamic_data, $translated_titles);
    84 
    85             wp_enqueue_script(
    86                 'visualwebs-ml-app-remote-js',
    87                 'https://ml.visualwebs.eu/app.js',
    88                 array(),
    89                 false,
    90                 ['in_footer' => true, 'strategy' => 'defer']
    91             );
    92 
    93             wp_enqueue_script(
    94                 'visualwebs-widget-js',
    95                 plugin_dir_url(__FILE__) . '../../../assets/js/widget.js',
    96                 array(),
    97                 false,
    98                 ['in_footer' => true, 'strategy' => 'defer']
    99             );
    100 
    101             wp_localize_script('visualwebs-widget-js', 'visualwebsAiWidgetData', $final_data);
    102 
    103         }
    104 
    105         // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required for this specific use case.
    106         if (isset($_GET['page']) && $_GET['page'] === 'visualwebs-ml-dashboard') {
    107 
    108             if ($this->dashboard->getIsVisualwebsMlServicesEnabled()) {
    109 
    110                 $stats = $this->dashboard->getStats();
    111                 $userLanguage = $this->dashboard->getCurrentAdminUserLanguageShort();
    112                 $currency_symbol =  html_entity_decode($this->dashboard->getStoreCurrencySign());
    113                 $nonce = $this->dashboard->getAppNonce();
    114                 $clear_cache_url = esc_url(admin_url('admin.php?page=visualwebs-ml-dashboard&clear_cache=true&_wpnonce=' . wp_create_nonce('clear_cache_action')));
    115 
    116                 wp_enqueue_script(
    117                     'visualwebs-dashboard-js',
    118                     plugin_dir_url(__FILE__) . '../../../assets/js/dashboard.js',
    119                     array(),
    120                     false,
    121                     ['in_footer' => true, 'strategy' => 'defer']
    122                 );
    123 
    124                 wp_localize_script('visualwebs-dashboard-js', 'visualwebsAiDashboardData', [
    125                     'stats' => $stats,
    126                     'currencySymbol' => $currency_symbol,
    127                     'clearCacheUrl' => $clear_cache_url,
    128                     'userLanguage' => $userLanguage,
    129                     'nonce' => $nonce
    130                 ]);
    131 
    132             }
    133         }
    134 
    135         // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required for this specific use case.
    136         if (isset($_GET['page']) && $_GET['page'] === 'visualwebs-ml-semantic-add') {
    137             wp_enqueue_script(
    138                 'visualwebs-ml-semantic-search',
    139                 plugin_dir_url(__FILE__) . '../../../assets/js/semantic-search.js',
    140                 array('jquery'),
    141                 false,
    142                 ['in_footer' => true, 'strategy' => 'defer']
    143             );
    144 
    145             wp_localize_script(
    146                 'visualwebs-ml-semantic-search',
    147                 'visualwebs_ml_search',
    148                 array(
    149                     'ajax_url' => admin_url('admin-ajax.php')
    150                 )
    151             );
    152 
    153         }
    154 
     22        // Legacy widgets and dashboard removed in 5.5.0 (now managed in SaaS dashboard)
     23        // Legacy chatbot removed in 5.5.0 (now uses n8n-chatbot-widget.php template)
     24        // Legacy semantic search admin removed in 5.5.0
     25        // Legacy admin styles removed in 5.5.0
    15526    }
    15627
     
    16031    public function enqueue_frontend_scripts()
    16132    {
    162         if ($this->chatWidget->getIsChatbotEnabled()) {
    163             $chatApiUrl = $this->chatWidget->getChatApiUrl();
    164             $chatbotCurrentPageInfo = $this->chatWidget->getChatbotCurrentPageInfoForWidget();
    165             $linksColor =  $this->chatWidget->getChatbotMainBackgroundColor();
    166             $mainBackgroundColor = $this->chatWidget->getChatbotMainBackgroundColor();
    167             $chatbotHeaderTitle = $this->chatWidget->getChatbotHeaderTitle();
    168             $chatbotDisclaimerUrl = $this->chatWidget->getChatbotDisclaimerUrl();
    169             $chatbotOfflineMessage = $this->chatWidget->getChatbotOfflineMessage();
    170             $chatbotLogoUrl = $this->chatWidget->getChatbotLogoUrl();
    171             $userLanguage = $this->chatWidget->getCurrentFrontendLanguageShort();
    172 
    173             wp_enqueue_script(
    174                 'visualwebs-ml-app-remote-js',
    175                 'https://ml.visualwebs.eu/app.js',
    176                 array(),
    177                 false,
    178                 ['in_footer' => true, 'strategy' => 'defer']
    179             );
    180 
    181             wp_enqueue_script('visualwebs-chatbot-js', plugin_dir_url(__FILE__) . '../../../assets/js/chatbot.js', ['visualwebs-ml-app-remote-js'], false, ['in_footer' => true, 'strategy' => 'defer']);
    182 
    183             $inline_data = 'var visualwebsAiChatbotData = ' . wp_json_encode([
    184               "chatApiUrl" => $chatApiUrl,
    185               "uniqueSessionId" => '',
    186               "chatbotCurrentPageInfo" => $chatbotCurrentPageInfo,
    187               "mainBackgroundColor" => $mainBackgroundColor,
    188               "linksColor" => $linksColor,
    189               "chatbotHeaderTitle" => $chatbotHeaderTitle,
    190               "chatbotDisclaimerUrl" => $chatbotDisclaimerUrl,
    191               "chatbotOfflineMessage" => $chatbotOfflineMessage,
    192               "chatbotLogoUrl" => $chatbotLogoUrl,
    193               "userLanguage" => $userLanguage,
    194               'restUrl' => esc_url_raw(rest_url('visualwebs-ml/v1/get-nonce'))
    195             ]) . ';';
    196 
    197             wp_add_inline_script('visualwebs-chatbot-js', $inline_data, 'before');
    198         }
     33        // Legacy chatbot enqueue removed in 5.5.0
     34        // Now uses n8n-chatbot-widget.php template loaded via wp_footer hook
     35        // See: class-visualwebs-ml.php render_frontend_chatbot_widget()
    19936    }
    20037}
  • visualwebs-ml/tags/5.5.0/vendor/visualwebs-ml/Helper/Visualwebs.php

    r3337157 r3476789  
    111111
    112112    /**
     113     * Sends payload to an explicit webhook URL.
     114     *
     115     * @param string $url
     116     * @param array  $data
     117     * @param array  $headers
     118     * @return array
     119     */
     120    public function sendWebhookRequest($url, $data = [], $headers = [])
     121    {
     122        $visualwebsApi = $this->getVisualwebsInstance();
     123        return $visualwebsApi->sendWebhookRequest($url, $data, $headers);
     124    }
     125
     126    /**
    113127     * Sends a vector upsert request to the Visualwebs API.
    114128     *
  • visualwebs-ml/tags/5.5.0/vendor/visualwebs-ml/Model/Api/Visualwebs.php

    r3320111 r3476789  
    9797
    9898    /**
     99     * Sends a request to an explicit webhook URL (used by SaaS chatbot n8n endpoint).
     100     *
     101     * @param string $url
     102     * @param array  $data
     103     * @param array  $extraHeaders
     104     * @return array
     105     */
     106    public function sendWebhookRequest($url, $data = [], $extraHeaders = [])
     107    {
     108        if (empty($url)) {
     109            return [
     110                'error' => true,
     111                'message' => 'Missing webhook URL',
     112            ];
     113        }
     114
     115        $headers = [
     116            'Content-Type' => 'application/json',
     117            'Accept' => 'application/json',
     118        ];
     119
     120        // Keep compatibility with existing API key auth model.
     121        if (!empty($this->apiKey)) {
     122            $headers['Authorization'] = 'Bearer ' . $this->apiKey;
     123            $headers['x-api-key'] = $this->apiKey;
     124        }
     125
     126        if (!empty($extraHeaders) && is_array($extraHeaders)) {
     127            $headers = array_merge($headers, $extraHeaders);
     128        }
     129
     130        $response = wp_remote_post(
     131            $url,
     132            [
     133                'headers' => $headers,
     134                'timeout' => 20,
     135                'body' => wp_json_encode($data),
     136            ]
     137        );
     138
     139        if (is_wp_error($response)) {
     140            return [
     141                'error' => true,
     142                'message' => $response->get_error_message(),
     143            ];
     144        }
     145
     146        $status = (int) wp_remote_retrieve_response_code($response);
     147        $body = wp_remote_retrieve_body($response);
     148        $decoded = json_decode($body, true);
     149
     150        if ($status < 200 || $status >= 300) {
     151            return [
     152                'error' => true,
     153                'message' => 'Webhook response status: ' . $status,
     154                'raw' => is_array($decoded) ? $decoded : $body,
     155            ];
     156        }
     157
     158        return is_array($decoded) ? $decoded : ['result' => (string) $body];
     159    }
     160
     161    /**
    99162     * Sends a spam prediction request to the specified endpoint.
    100163     *
  • visualwebs-ml/tags/5.5.0/vendor/visualwebs-ml/Model/MLModel.php

    r3326860 r3476789  
    658658
    659659        $tomorrow = gmdate('Y-m-d', strtotime('+1 day'));
    660         $min_pct = $this->mainHelper->getDynamicPricingMinProfit();
    661         $max_pct = $this->mainHelper->getDynamicPricingMaxProfit();
     660        // Default profit margins (config removed, now managed in SaaS)
     661        $min_pct = 10;
     662        $max_pct = 35;
    662663        $result = [];
    663664
  • visualwebs-ml/tags/5.5.0/visualwebs-ml.php

    r3337202 r3476789  
    44Requires Plugins: redux-framework, woocommerce
    55Description: Plugin to embed chatGPT, chatbot, and Machine Learning Widgets in WP.
    6 Version: 5.4.3
     6Version: 5.5.0
    77Requires PHP: 7.4
    88Author: Visualwebs
    99Author URI: https://visualwebs.eu/product/ai-cloud-suite/
    10 Tested up to: 6.8
     10Tested up to: 6.9
    1111License: GPLv2 or later
    1212License URI: https://visualwebs.eu/terms-and-conditions/
  • visualwebs-ml/trunk/class-visualwebs-ml.php

    r3322994 r3476789  
    9494
    9595        add_action( 'plugins_loaded', array( 'Visualwebs\ML\Admin', 'init' ) );
     96        add_action( 'wp_footer', array( $this, 'render_frontend_chatbot_widget' ), 999 );
    9697        add_action(
    9798            'redux/options/visualwebs_ml_options/saved',
     
    135136                register_rest_route(
    136137                    'visualwebs-ml/v1',
     138                    'chat-api/admin-call',
     139                    array(
     140                        'methods'             => 'POST',
     141                        'callback'            => array( $this, 'handle_admin_chat_api_call' ),
     142                        'permission_callback' => array( $this, 'check_admin_chat_permissions' ),
     143                    )
     144                );
     145
     146                register_rest_route(
     147                    'visualwebs-ml/v1',
    137148                    'ml-api/call',
    138149                    array(
     
    156167        require_once __DIR__ . '/includes/cronjobs.php';
    157168        require_once __DIR__ . '/includes/hooks/entity-save.php';
     169        require_once __DIR__ . '/includes/hooks/event-dispatcher.php';
    158170        require_once __DIR__ . '/includes/helpers/content-generator.php';
     171        require_once __DIR__ . '/includes/feeds/class-insights-feed-generator.php';
    159172    }
    160173
     
    174187
    175188    /**
     189     * Render chatbot widget in frontend footer.
     190     *
     191     * @since 5.5.0
     192     */
     193    public function render_frontend_chatbot_widget() {
     194        require plugin_dir_path( __FILE__ ) . 'templates/n8n-chatbot-widget.php';
     195    }
     196
     197    /**
    176198     * Handle chat API callback
    177199     *
     
    189211     */
    190212    public function handle_chat_api_call( WP_REST_Request $request ) {
    191         $response = $this->chatbot_api_helper->execute( $request->get_query_params() );
     213        $response = $this->chatbot_api_helper->execute( $request->get_json_params() );
     214        return rest_ensure_response( $response );
     215    }
     216
     217    /**
     218     * Validate permissions for admin chatbot endpoint.
     219     *
     220     * @param WP_REST_Request $request Request object.
     221     * @return bool|WP_Error
     222     */
     223    public function check_admin_chat_permissions( WP_REST_Request $request ) {
     224        if ( ! is_user_logged_in() || ! current_user_can( 'manage_options' ) ) {
     225            return new WP_Error( 'vwml_forbidden', __( 'You are not allowed to use admin chatbot.', 'visualwebs-ml' ), array( 'status' => 403 ) );
     226        }
     227
     228        $nonce = sanitize_text_field( (string) $request->get_param( '_vwml_nonce' ) );
     229        if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'visualwebs_ml_admin_chat' ) ) {
     230            return new WP_Error( 'vwml_invalid_nonce', __( 'Invalid admin chatbot nonce.', 'visualwebs-ml' ), array( 'status' => 403 ) );
     231        }
     232
     233        return true;
     234    }
     235
     236    /**
     237     * Handle admin chatbot API call routed through secure local endpoint.
     238     *
     239     * @param WP_REST_Request $request The REST API request object.
     240     * @return WP_REST_Response
     241     */
     242    public function handle_admin_chat_api_call( WP_REST_Request $request ) {
     243        $response = $this->chatbot_api_helper->execute_admin( $request->get_json_params() );
    192244        return rest_ensure_response( $response );
    193245    }
  • visualwebs-ml/trunk/composer.json

    r3319473 r3476789  
    99        }
    1010    },
    11     "require": {
    12         "smalot/pdfparser": "^0.18",
    13         "phpoffice/phpword": "^1.1"
    14     },
    1511    "scripts": {
    1612        "post-install-cmd": [
  • visualwebs-ml/trunk/includes/cronjobs.php

    r3330758 r3476789  
    2323use Visualwebs\ML\Helper\Visualwebs as ApiHelper;
    2424use Visualwebs\ML\Helper\Chatbot as ChatbotHelper;
    25 use Visualwebs\ML\Helper\Pinecone as PineconeHelper;
    26 use Visualwebs\ML\Helper\OpenAi as OpenAiHelper;
    2725
    2826register_activation_hook(
    2927    plugin_dir_path( __DIR__ ) . 'visualwebs-ml.php',
    3028    function () {
    31         if ( ! wp_next_scheduled( 'visualwebs_ml_semantic_queue_processor' ) ) {
    32             wp_schedule_event( time(), 'every_five_minutes', 'visualwebs_ml_semantic_queue_processor' );
    33         }
    34         if ( ! wp_next_scheduled( 'visualwebs_ml_daily_dynamic_pricing' ) ) {
    35             $next_run = strtotime( 'tomorrow 07:00:00' );
    36             wp_schedule_event( $next_run, 'daily', 'visualwebs_ml_daily_dynamic_pricing' );
    37         }
    38         if ( ! wp_next_scheduled( 'visualwebs_ml_check_dynamic_pricing_batch_jobs' ) ) {
    39             wp_schedule_event( time(), 'every_five_minutes', 'visualwebs_ml_check_dynamic_pricing_batch_jobs' );
    40         }
    41         if ( ! wp_next_scheduled( 'visualwebs_ml_weekly_dynamic_pricing_training' ) ) {
    42             $next_run = strtotime( 'tomorrow 00:00:00' );
    43             wp_schedule_event( $next_run, 'weekly', 'visualwebs_ml_weekly_dynamic_pricing_training' );
     29        // Feed generation cron jobs
     30        if ( ! wp_next_scheduled( 'visualwebs_ml_product_feed' ) ) {
     31            wp_schedule_event( time(), 'hourly', 'visualwebs_ml_product_feed' );
     32        }
     33        if ( ! wp_next_scheduled( 'visualwebs_ml_page_feed' ) ) {
     34            $next_run = strtotime( 'tomorrow 02:00:00' );
     35            wp_schedule_event( $next_run, 'daily', 'visualwebs_ml_page_feed' );
     36        }
     37        if ( ! wp_next_scheduled( 'visualwebs_ml_sales_feed' ) ) {
     38            $next_run = strtotime( 'tomorrow 02:00:00' );
     39            wp_schedule_event( $next_run, 'daily', 'visualwebs_ml_sales_feed' );
     40        }
     41        if ( ! wp_next_scheduled( 'visualwebs_ml_insights_feed' ) ) {
     42            $next_run = strtotime( 'tomorrow 03:00:00' );
     43            wp_schedule_event( $next_run, 'daily', 'visualwebs_ml_insights_feed' );
    4444        }
    4545    }
     
    4949    plugin_dir_path( __DIR__ ) . 'visualwebs-ml.php',
    5050    function () {
    51         wp_clear_scheduled_hook( 'visualwebs_ml_semantic_queue_processor' );
    52         wp_clear_scheduled_hook( 'visualwebs_ml_daily_dynamic_pricing' );
    53         wp_clear_scheduled_hook( 'visualwebs_ml_check_dynamic_pricing_batch_jobs' );
    54         wp_clear_scheduled_hook( 'visualwebs_ml_weekly_dynamic_pricing_training' );
    55     }
    56 );
    57 
    58 add_filter(
    59     'cron_schedules', // phpcs:ignore WordPress.WP.CronInterval.CronSchedulesInterval
    60     function ( $schedules ) {
    61         $schedules['every_five_minutes'] = array(
    62             'interval' => 300,
    63             'display'  => __( 'Every 5 Minutes', 'visualwebs-ml' ),
    64         );
    65         return $schedules;
    66     }
    67 );
    68 
    69 add_action(
    70     'visualwebs_ml_semantic_queue_processor',
    71     function () {
    72         global $wpdb;
    73 
    74         $table_name  = $wpdb->prefix . 'visualwebs_ml_semantic_queue';
    75         $batch_limit = 5;
    76 
    77         // Clear completed and disabled jobs.
    78         // phpcs:ignore WordPress.DB.DirectDatabaseQuery
    79         $wpdb->query(
    80             $wpdb->prepare(
    81                 'DELETE FROM %i
    82              WHERE job_status = %d AND job_sync_status = %d',
    83                 $table_name,
    84                 0,
    85                 1
    86             )
    87         );
    88 
    89         // Fetch jobs that are not completed.
    90         // phpcs:ignore WordPress.DB.DirectDatabaseQuery
    91         $jobs = $wpdb->get_results(
    92             $wpdb->prepare(
    93                 'SELECT * FROM %i
    94              WHERE job_sync_status != %d
    95              LIMIT %d',
    96                 $table_name,
    97                 1,
    98                 $batch_limit
    99             )
    100         );
    101 
    102         foreach ( $jobs as $job ) {
    103             // Process each job.
    104             visualwebs_ml_process_queue_item( $job );
    105 
    106             // Mark the job as completed.
    107             // phpcs:ignore WordPress.DB.DirectDatabaseQuery
    108             $wpdb->update(
    109                 $table_name,
    110                 array( 'job_sync_status' => 1 ), // Mark as completed.
    111                 array( 'job_id' => $job->job_id )
    112             );
    113         }
    114     }
    115 );
    116 
    117 add_action(
    118     'visualwebs_ml_daily_dynamic_pricing',
    119     'visualwebs_ml_dynamic_pricing'
     51        wp_clear_scheduled_hook( 'visualwebs_ml_product_feed' );
     52        wp_clear_scheduled_hook( 'visualwebs_ml_page_feed' );
     53        wp_clear_scheduled_hook( 'visualwebs_ml_sales_feed' );
     54        wp_clear_scheduled_hook( 'visualwebs_ml_insights_feed' );
     55    }
    12056);
    12157
     
    250186
    251187add_action(
    252     'visualwebs_ml_weekly_dynamic_pricing_training',
    253     function () {
    254         do_action( 'visualwebs_ml_train_dynamic_pricing' );
    255     }
    256 );
    257 
    258 add_action(
    259188    'visualwebs_ml_train_dynamic_pricing',
    260189    'visualwebs_ml_train_dynamic_pricing'
     
    382311
    383312
    384 add_action(
    385     'visualwebs_ml_check_dynamic_pricing_batch_jobs',
    386     function () {
    387         global $wpdb;
    388         $helper     = new \Visualwebs\ML\Helper\Data();
    389         $api_helper = new ApiHelper();
    390 
    391         if ( ! $helper->getIsDynamicPricingEnabled() ) {
    392             return false;
    393         }
    394         // Fetch all pending jobs.
    395         $table_name = $wpdb->prefix . 'visualwebs_ml_dynamic_pricing_jobs';
    396         // phpcs:ignore WordPress.DB.DirectDatabaseQuery, phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
    397         $pending_jobs = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i WHERE job_status = %s', $table_name, 'processing' ) );
    398         foreach ( $pending_jobs as $job ) {
    399             $job_id   = $job->job_id;
    400             $job_hash = $job->job_hash;
    401             $data     = array(
    402                 'job_hash' => $job_hash,
    403             );
    404 
    405             $response = $api_helper->sendDynamicPricingBatchJobStatusRequest( $data );
    406 
    407             if ( 'done' === $response['status'] && ! empty( $response['job_id'] ) ) {
    408 
    409                 if ( 'predict' === $response['type'] ) {
     313function visualwebs_ml_check_dynamic_pricing_batch_jobs() {
     314    global $wpdb;
     315    $helper     = new \Visualwebs\ML\Helper\Data();
     316    $api_helper = new ApiHelper();
     317
     318    if ( ! $helper->getIsDynamicPricingEnabled() ) {
     319        return false;
     320    }
     321    // Fetch all pending jobs.
     322    $table_name = $wpdb->prefix . 'visualwebs_ml_dynamic_pricing_jobs';
     323    // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
     324    $pending_jobs = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i WHERE job_status = %s', $table_name, 'processing' ) );
     325    foreach ( $pending_jobs as $job ) {
     326        $job_id   = $job->job_id;
     327        $job_hash = $job->job_hash;
     328        $data     = array(
     329            'job_hash' => $job_hash,
     330        );
     331
     332        $response = $api_helper->sendDynamicPricingBatchJobStatusRequest( $data );
     333
     334        if ( 'done' === $response['status'] && ! empty( $response['job_id'] ) ) {
     335
     336            if ( 'predict' === $response['type'] ) {
    410337
    411338                    $results      = $api_helper->downloadDynamicPricingBatchResults( $response['job_id'] );
     
    439366
    440367                        if ( $new_price <= $cogs ) {
    441                             $min_pct   = min( 1, $helper->getDynamicPricingMinProfit() );
     368                            // Default minimum profit 10% (config for min_profit was removed, now managed in SaaS)
     369                            $min_pct   = 10;
    442370                            $new_price = $cogs * ( 1 + ( $min_pct / 100 ) );
    443371                        }
     
    487415        }
    488416    }
    489 );
    490417
    491418/**
     
    500427function visualwebs_ml_process_queue_item( $job ) {
    501428    global $wpdb;
    502     $helper          = new Helper();
    503     $pinecone_helper = new PineconeHelper();
    504     $opean_ai_helper = new OpenAiHelper();
    505     $api_helper      = new ApiHelper();
    506     $chatbot_helper  = new ChatbotHelper();
    507 
    508     $use_customer_api_keys_enabled = $helper->getUseCustomerApiKeysEnabled();
    509     $pinecone_api_key              = $pinecone_helper->getPineconeApiKey();
    510     $pinecone_index_host           = $pinecone_helper->getPineconeIndexHost();
    511     $pinecone_namespace            = $pinecone_helper->getPineconeNamespace();
    512     $openai_api_key                = $opean_ai_helper->getApiKey();
    513     $domain_namespace              = $helper->getDomainNamespace();
    514 
    515     $params = $use_customer_api_keys_enabled ? array(
    516         'openai_api_key'      => $openai_api_key,
    517         'pinecone_api_key'    => $pinecone_api_key,
    518         'pinecone_index_host' => $pinecone_index_host,
    519         'pinecone_namespace'  => $pinecone_namespace,
    520     ) : array(
    521         'pinecone_namespace' => $domain_namespace,
     429    $helper         = new Helper();
     430    $api_helper     = new ApiHelper();
     431    $chatbot_helper = new ChatbotHelper();
     432
     433    // Legacy semantic search processing (now managed in SaaS)
     434    $params = array(
     435        'pinecone_namespace' => $helper->getDomainNamespace(),
    522436    );
    523437
     
    675589    );
    676590}
     591
     592// Feed generation cron job handlers
     593
     594add_action('visualwebs_ml_product_feed', function() {
     595    require_once plugin_dir_path(__FILE__) . 'feeds/class-product-feed-generator.php';
     596    $generator = new VisualwebsML_ProductFeedGenerator();
     597    $generator->generate();
     598});
     599
     600add_action('visualwebs_ml_page_feed', function() {
     601    require_once plugin_dir_path(__FILE__) . 'feeds/class-page-feed-generator.php';
     602    $generator = new VisualwebsML_PageFeedGenerator();
     603    $generator->generate();
     604});
     605
     606add_action('visualwebs_ml_sales_feed', function() {
     607    require_once plugin_dir_path(__FILE__) . 'feeds/class-sales-feed-generator.php';
     608    $generator = new VisualwebsML_SalesFeedGenerator();
     609    $generator->generate();
     610});
     611
     612add_action('visualwebs_ml_insights_feed', function() {
     613    require_once plugin_dir_path(__FILE__) . 'feeds/class-insights-feed-generator.php';
     614    $generator = new VisualwebsML_InsightsFeedGenerator();
     615    $generator->generate();
     616});
  • visualwebs-ml/trunk/includes/redux-config.php

    r3325171 r3476789  
    2323}
    2424
    25 $opt_name = 'visualwebs_ml_options';
    26 
    27 $args = array(
    28     'opt_name'           => $opt_name,
     25$visualwebs_ml_opt_name = 'visualwebs_ml_options';
     26
     27$visualwebs_ml_args = array(
     28    'opt_name'           => $visualwebs_ml_opt_name,
    2929    'display_name'       => 'Visualwebs AI Cloud Suite Options',
    3030    'menu_type'          => 'submenu',
     
    5757);
    5858
    59 Redux::setArgs( $opt_name, $args );
     59Redux::setArgs( $visualwebs_ml_opt_name, $visualwebs_ml_args );
    6060
    6161// Extension Options Group.
    6262Redux::setSection(
    63     $opt_name,
     63    $visualwebs_ml_opt_name,
    6464    array(
    6565        'title'  => __( 'Extension Options', 'visualwebs-ml' ),
     
    8282// Visualwebs AI Cloud Suite API Group.
    8383Redux::setSection(
    84     $opt_name,
     84    $visualwebs_ml_opt_name,
    8585    array(
    8686        'title'  => __( 'API', 'visualwebs-ml' ),
     
    107107            ),
    108108            array(
    109                 'id'      => 'use_customer_api_keys',
    110                 'type'    => 'switch',
    111                 'title'   => __( 'Use your own API Keys', 'visualwebs-ml' ),
    112                 'default' => false,
    113                 'on'      => 'Enabled',
    114                 'off'     => 'Disabled',
    115                 'desc'    => __( 'Enable only if you want to use your OpenAI and Pinecone keys.', 'visualwebs-ml' ),
    116             ),
    117         ),
    118     )
    119 );
    120 
    121 // User Widgets Group.
    122 Redux::setSection(
    123     $opt_name,
    124     array(
    125         'title'  => __( 'User Widgets', 'visualwebs-ml' ),
    126         'id'     => 'widgets',
    127         'desc'   => __( 'Settings for User Widgets.', 'visualwebs-ml' ),
    128         'icon'   => 'el el-th-large',
    129         'fields' => array(
    130             array(
    131                 'id'           => 'user_widgets',
    132                 'type'         => 'repeater',
    133                 'group_values' => true,
    134                 'title'        => __( 'Widgets', 'visualwebs-ml' ),
    135                 'desc'         => __( 'Configure user widgets.', 'visualwebs-ml' ),
    136                 'fields'       => array(
    137                     array(
    138                         'id'      => 'widget_title',
    139                         'type'    => 'text',
    140                         'title'   => __( 'Title', 'visualwebs-ml' ),
    141                         'default' => '',
    142                     ),
    143                     array(
    144                         'id'      => 'widget_instructions',
    145                         'type'    => 'text',
    146                         'title'   => __( 'Instructions', 'visualwebs-ml' ),
    147                         'default' => '',
    148                     ),
    149                     array(
    150                         'id'      => 'widget_render_el',
    151                         'type'    => 'text',
    152                         'title'   => __( 'Render Element', 'visualwebs-ml' ),
    153                         'default' => '',
    154                     ),
    155                     array(
    156                         'id'      => 'widget_source_el',
    157                         'type'    => 'text',
    158                         'title'   => __( 'Source Element', 'visualwebs-ml' ),
    159                         'default' => '',
    160                     ),
     109                'id'       => 'store_id',
     110                'type'     => 'text',
     111                'title'    => __( 'Store ID (SaaS UUID)', 'visualwebs-ml' ),
     112                'desc'     => __( 'UUID from SaaS Dashboard when registering this store.', 'visualwebs-ml' ),
     113                'required' => array( 'api_enabled', '=', '1' ),
     114                'validate' => 'no_html',
     115            ),
     116        ),
     117    )
     118);
     119
     120// Workflow Integration Group.
     121Redux::setSection(
     122    $visualwebs_ml_opt_name,
     123    array(
     124        'title'  => __( 'Workflow Integration', 'visualwebs-ml' ),
     125        'id'     => 'workflow',
     126        'desc'   => __( 'Configure event delivery to SaaS and n8n workflows.', 'visualwebs-ml' ),
     127        'icon'   => 'el el-random',
     128        'fields' => array(
     129            array(
     130                'id'       => 'enable_workflows',
     131                'type'     => 'switch',
     132                'title'    => __( 'Enable Workflows', 'visualwebs-ml' ),
     133                'default'  => false,
     134                'on'       => 'Enabled',
     135                'off'      => 'Disabled',
     136                'required' => array( 'api_enabled', '=', '1' ),
     137            ),
     138            array(
     139                'id'       => 'enabled_events',
     140                'type'     => 'checkbox',
     141                'title'    => __( 'Enabled Events', 'visualwebs-ml' ),
     142                'options'  => array(
     143                    'woocommerce_new_order'          => __( 'Order Created', 'visualwebs-ml' ),
     144                    'woocommerce_order_status_changed' => __( 'Order Status Changed', 'visualwebs-ml' ),
     145                    'user_register'                  => __( 'User Registered', 'visualwebs-ml' ),
     146                    'woocommerce_product_updated'    => __( 'Product Updated', 'visualwebs-ml' ),
    161147                ),
    162                 'default'      => array(),
     148                'default'  => array(
     149                    'woocommerce_new_order'          => '1',
     150                    'woocommerce_order_status_changed' => '1',
     151                ),
     152                'required' => array( 'enable_workflows', '=', '1' ),
     153            ),
     154            array(
     155                'id'       => 'anonymize_payload',
     156                'type'     => 'switch',
     157                'title'    => __( 'Anonymize Personal Data (GDPR)', 'visualwebs-ml' ),
     158                'default'  => true,
     159                'on'       => 'Enabled',
     160                'off'      => 'Disabled',
     161                'desc'     => __( 'Redacts personal fields before sending event payloads.', 'visualwebs-ml' ),
     162                'required' => array( 'enable_workflows', '=', '1' ),
    163163            ),
    164164        ),
     
    168168// ChatGPT Group.
    169169Redux::setSection(
    170     $opt_name,
     170    $visualwebs_ml_opt_name,
    171171    array(
    172172        'title'  => __( 'ChatGPT', 'visualwebs-ml' ),
     
    176176        'fields' => array(
    177177            array(
    178                 'id'       => 'chatgpt_api_key',
    179                 'type'     => 'text',
    180                 'title'    => __( 'Open AI Api key', 'visualwebs-ml' ),
    181                 'required' => array( 'use_customer_api_keys', '=', '1' ),
    182             ),
    183             array(
    184178                'id'      => 'chatbot_enabled',
    185179                'type'    => 'switch',
     
    191185            ),
    192186            array(
    193                 'id'      => 'debug_enabled',
    194                 'type'    => 'switch',
    195                 'title'   => __( 'Enables the Chatbot debug', 'visualwebs-ml' ),
    196                 'default' => false,
    197                 'on'      => 'Enabled',
    198                 'off'     => 'Disabled',
    199                 'desc'    => __( 'Writes Chatbot information in logs/visualwebs_ml.log', 'visualwebs-ml' ),
    200             ),
    201             array(
    202                 'id'      => 'chatbot_header_title',
    203                 'type'    => 'text',
    204                 'default' => 'Support',
    205                 'title'   => __( 'Chatbot header', 'visualwebs-ml' ),
    206                 'desc'    => __( 'The title to show in the header of chat widget, i.e. Support, mysite.com Support..', 'visualwebs-ml' ),
    207             ),
    208             array(
    209                 'id'    => 'chatbot_logo',
    210                 'type'  => 'media',
    211                 'title' => __( 'Upload Logo', 'visualwebs-ml' ),
    212                 'desc'  => __( 'Allowed file types: jpg, jpeg, gif, png', 'visualwebs-ml' ),
    213             ),
    214             array(
    215                 'id'       => 'chatbot_main_background_color',
    216                 'type'     => 'color',
    217                 'title'    => __( 'Color', 'visualwebs-ml' ),
    218                 'default'  => '#ffffff',
    219                 'required' => array( 'chatbot_enabled', '=', '1' ),
    220                 'validate' => 'not_empty',
    221             ),
    222             array(
    223                 'id'       => 'chatbot_context',
    224                 'type'     => 'textarea',
    225                 'default'  => 'Act as E-commerce support team',
    226                 'title'    => __( 'Chatbot context', 'visualwebs-ml' ),
    227                 'desc'     => __( 'Provide clear instructions for the chatbot\'s behavior.', 'visualwebs-ml' ),
    228                 'required' => array( 'chatbot_enabled', '=', '1' ),
    229                 'validate' => 'not_empty',
    230             ),
    231             array(
    232                 'id'      => 'chatbot_offline_message',
    233                 'type'    => 'text',
    234                 'default' => 'Our support system is OFF now, please try later.',
    235                 'title'   => __( 'Message to show to customers when Bot is Offline.', 'visualwebs-ml' ),
    236                 'desc'    => __( 'Example: Our support system is OFF now, please try later.', 'visualwebs-ml' ),
    237             ),
    238             array(
    239                 'id'    => 'chatbot_disclaimer_url',
    240                 'type'  => 'text',
    241                 'title' => __( 'Disclaimer url', 'visualwebs-ml' ),
    242                 'desc'  => __( 'Specify the slug of the disclaimer page.', 'visualwebs-ml' ),
    243             ),
    244         ),
    245     )
    246 );
    247 
    248 // Semantic Search Group.
    249 Redux::setSection(
    250     $opt_name,
    251     array(
    252         'title'  => __( 'Semantic Search', 'visualwebs-ml' ),
    253         'id'     => 'semantic_search',
    254         'desc'   => __( 'Settings for Semantic Search.', 'visualwebs-ml' ),
    255         'icon'   => 'el el-search',
    256         'fields' => array(
    257             array(
    258                 'id'      => 'semantic_search_enabled',
    259                 'type'    => 'switch',
    260                 'title'   => __( 'Enables semantic search', 'visualwebs-ml' ),
    261                 'default' => true,
    262                 'on'      => 'Enabled',
    263                 'off'     => 'Disabled',
    264                 'desc'    => __( 'Enables the Semantic search. Requires a vector database connection.', 'visualwebs-ml' ),
    265             ),
    266             array(
    267                 'id'       => 'vector_database_service',
    268                 'type'     => 'text',
    269                 'title'    => __( 'Vector database service', 'visualwebs-ml' ),
    270                 'required' => array( 'use_customer_api_keys', '=', '1' ),
    271                 'desc'     => __( 'Use a free Pinacone account as starting point. Note you would need a paid subscription for large websites.', 'visualwebs-ml' ),
    272             ),
    273             array(
    274                 'id'       => 'vector_database_api_key',
    275                 'type'     => 'text',
    276                 'title'    => __( 'Vector database Api key', 'visualwebs-ml' ),
    277                 'required' => array( 'use_customer_api_keys', '=', '1' ),
    278             ),
    279             array(
    280                 'id'       => 'vector_database_index_host',
    281                 'type'     => 'text',
    282                 'title'    => __( 'Vector database index host', 'visualwebs-ml' ),
    283                 'required' => array( 'use_customer_api_keys', '=', '1' ),
    284                 'desc'     => __( 'Find it in Pinecone panel, select the desired index and copy the host.', 'visualwebs-ml' ),
    285             ),
    286             array(
    287                 'id'       => 'vector_index_namespace',
    288                 'type'     => 'text',
    289                 'title'    => __( 'Vector index namespace', 'visualwebs-ml' ),
    290                 'required' => array( 'use_customer_api_keys', '=', '1' ),
    291                 'desc'     => __( 'Optional for paid vector accounts. Leave empty as general rule.', 'visualwebs-ml' ),
    292             ),
    293             array(
    294                 'id'      => 'vector_min_search_score',
    295                 'type'    => 'text',
    296                 'title'   => __( 'Vector min search score', 'visualwebs-ml' ),
    297                 'default' => '0.75',
    298                 'desc'    => __( 'Start using 0.75. Use lower values to use vector data more frequently.', 'visualwebs-ml' ),
    299             ),
    300             array(
    301                 'id'      => 'vector_store_dynamic_data',
    302                 'type'    => 'switch',
    303                 'title'   => __( 'Store Product Price and Stock Data', 'visualwebs-ml' ),
    304                 'default' => false,
    305                 'on'      => 'Enabled',
    306                 'off'     => 'Disabled',
    307                 'desc'    => __( 'Enable if your catalog doesn\'t change frequently. This will add product price and stock data to chatbot knowledge.', 'visualwebs-ml' ),
     187                'id'      => 'admin_enabled',
     188                'type'    => 'switch',
     189                'title'   => __( 'Enable admin widget', 'visualwebs-ml' ),
     190                'default' => false,
     191                'on'      => 'Enabled',
     192                'off'     => 'Disabled',
     193                'desc'    => __( 'Enable chatbot widget on admin screens.', 'visualwebs-ml' ),
    308194            ),
    309195        ),
     
    313199// Dynamic Pricing Group.
    314200Redux::setSection(
    315     $opt_name,
     201    $visualwebs_ml_opt_name,
    316202    array(
    317203        'title'  => __( 'SmartPricing AI', 'visualwebs-ml' ),
     
    344230                'desc'    => __( 'Automatically replaces catalog prices with daily AI predictions.', 'visualwebs-ml' ),
    345231            ),
    346             array(
    347                 'id'      => 'dynamic_pricing_min_profit',
    348                 'type'    => 'text',
    349                 'title'   => __( 'Minimum Profit (%)', 'visualwebs-ml' ),
    350                 'default' => '10',
    351                 'desc'    => __( 'Minimum profit margin as a percentage.', 'visualwebs-ml' ),
    352             ),
    353             array(
    354                 'id'      => 'dynamic_pricing_max_profit',
    355                 'type'    => 'text',
    356                 'title'   => __( 'Maximum Profit (%)', 'visualwebs-ml' ),
    357                 'default' => '35',
    358                 'desc'    => __( 'Maximum profit margin as a percentage.', 'visualwebs-ml' ),
    359             ),
    360         ),
    361     )
    362 );
     232        ),
     233    )
     234);
  • visualwebs-ml/trunk/readme.txt

    r3357523 r3476789  
    33Tags: AI, Chatbot, Machine Learning, Spam, ChatGPT
    44Requires at least: 6.2 
    5 Tested up to: 6.8 
     5Tested up to: 6.9 
    66Requires PHP: 7.4 
    7 Stable tag: 5.4.3
     7Stable tag: 5.5.0
    88License: GPLv2 or later 
    99License URI: https://visualwebs.eu/terms-and-conditions/ 
     
    8080
    8181== Changelog ==
    82 = 5.2.0 =
    83 * Initial release.
    84 = 5.3.0 =
    85 * Removed the minimum data requirement for Spam and Sentiment Analysis widgets. These can now display results even with just one review, without needing to train a model.
     82= 5.5.0 =
     83* Major Update: Migrated to SaaS architecture (matches Magento module functionality)
     84* New: n8n workflow integration with event system (order events, customer events, product events)
     85* New: Product feed generator with hourly cron job for semantic search sync
     86* New: Page feed generator with daily cron job
     87* New: Sales feed generator for SmartPricing AI training data
     88* New: Insights feed generator (`insights_feed.json`) for dashboard widgets
     89* New: Manual feed generation buttons in admin panel
     90* New: Chatbot admin/backend integration with secure admin endpoint (capability + nonce)
     91* Security: GDPR-compliant payload anonymization for workflows
     92* Improved: Simplified admin interface (dashboard + settings + feed generation)
     93* Deprecated: Database table access methods (use SaaS API instead)
     94* Deprecated: Semantic search and dynamic pricing queue grid classes (use SaaS dashboard)
     95* Performance: Removed legacy cron jobs and unused local processing paths
     96
     97= 5.4.3 =
     98* Added English/Spanish translation support for widgets and dashboard.
     99= 5.4.2 =
     100* Improved: Simplified chatbot security to ensure compatibility with caching plugins and language translators. Security checks are now handled by our remote service, which uses a license-based rate limiter for protection.
     101= 5.4.1 =
     102* New feature: Track SmartPricing AI training results together with prediction jobs for improved monitoring and transparency.
    86103= 5.4.0 =
    87104* New feature: AI-powered dynamic pricing to help you set the best product sale price and maximize WooCommerce profits.
    88105* New feature: Bulk append products, categories, or pages for faster AI processing and management.
    89106* New feature: Attach PDF, DOC, or text files to each semantic search item. Uploaded files are added to the chatbot's knowledge base.
    90 = 5.4.1 =
    91 * New feature: Track SmartPricing AI training results together with prediction jobs for improved monitoring and transparency.
    92 = 5.4.2 =
    93 * Improved: Simplified chatbot security to ensure compatibility with caching plugins and language translators. Security checks are now handled by our remote service, which uses a license-based rate limiter for protection.
    94 = 5.4.3 =
    95 * Added English/Spanish translation support for widgets and dashboard.
     107= 5.3.0 =
     108* Removed the minimum data requirement for Spam and Sentiment Analysis widgets. These can now display results even with just one review, without needing to train a model.
     109= 5.2.0 =
     110* Initial release.
    96111
    97112== License ==
  • visualwebs-ml/trunk/templates/ml-dashboard.php

    r3319473 r3476789  
    99 * @copyright Visualwebs Spain
    1010 * @license See https://visualwebs.eu/terms-and-conditions/ for license details.
     11 *
     12 * @since 5.5.0 - Migrated to SaaS architecture
    1113 */
    1214
     
    1921}
    2022
    21 use Visualwebs\ML\Block\Adminhtml\Dashboard;
    2223use Visualwebs\ML\Helper\Data as Helper;
    2324
    24 $dashboard = new Dashboard();
     25$visualwebs_ml_helper = new Helper();
    2526
    26 if ( ! $dashboard->getIsVisualwebsMlServicesEnabled() ) {
     27if ( ! $visualwebs_ml_helper->getIsVisualwebsMlServicesEnabled() ) {
    2728    echo '<div class="notice notice-warning"><p>';
    2829    echo esc_html__( 'Please purchase a subscription ', 'visualwebs-ml' );
     
    3536}
    3637
    37 if ( isset( $_GET['clear_cache'] ) ) {
    38     check_admin_referer( 'clear_cache_action' );
    39     if ( true === (bool) $_GET['clear_cache'] ) {
    40         delete_transient( Helper::CACHE_TRANSIENT );
    41     }
    42 }
     38$visualwebs_ml_dashboard_url = 'https://saas.visualwebs.eu/dashboard';
     39$visualwebs_ml_api_key = $visualwebs_ml_helper->getApiKey();
     40$visualwebs_ml_store_id = $visualwebs_ml_helper->getStoreId();
     41$visualwebs_ml_workflows_enabled = $visualwebs_ml_helper->getConfig('enable_workflows', false);
     42?>
     43
     44<div class="wrap">
     45    <h1><?php esc_html_e('Visualwebs AI Cloud Suite', 'visualwebs-ml'); ?></h1>
     46   
     47    <div class="card">
     48        <h2><?php esc_html_e('SaaS Dashboard', 'visualwebs-ml'); ?></h2>
     49        <p><?php esc_html_e('All AI features are managed from the centralized SaaS dashboard:', 'visualwebs-ml'); ?></p>
     50        <ul>
     51            <li><?php esc_html_e('Semantic Search Queue Management', 'visualwebs-ml'); ?></li>
     52            <li><?php esc_html_e('SmartPricing AI Jobs & Training', 'visualwebs-ml'); ?></li>
     53            <li><?php esc_html_e('n8n Workflow Monitoring', 'visualwebs-ml'); ?></li>
     54            <li><?php esc_html_e('Analytics & Insights', 'visualwebs-ml'); ?></li>
     55        </ul>
     56        <p>
     57            <a href="<?php echo esc_url($visualwebs_ml_dashboard_url); ?>" target="_blank" class="button button-primary button-hero">
     58                <?php esc_html_e('Open SaaS Dashboard', 'visualwebs-ml'); ?> →
     59            </a>
     60        </p>
     61    </div>
     62   
     63    <div class="card">
     64        <h2><?php esc_html_e('Configuration Status', 'visualwebs-ml'); ?></h2>
     65        <table class="widefat">
     66            <tbody>
     67                <tr>
     68                    <td><strong><?php esc_html_e('API Key', 'visualwebs-ml'); ?></strong></td>
     69                    <td>
     70                        <?php if ($visualwebs_ml_api_key): ?>
     71                            <span style="color: #46b450;">✓ <?php esc_html_e('Configured', 'visualwebs-ml'); ?></span>
     72                        <?php else: ?>
     73                            <span style="color: #dc3232;">✗ <?php esc_html_e('Missing', 'visualwebs-ml'); ?></span>
     74                        <?php endif; ?>
     75                    </td>
     76                </tr>
     77                <tr>
     78                    <td><strong><?php esc_html_e('Store ID', 'visualwebs-ml'); ?></strong></td>
     79                    <td>
     80                        <?php if ($visualwebs_ml_store_id): ?>
     81                            <span style="color: #46b450;">✓ <?php echo esc_html($visualwebs_ml_store_id); ?></span>
     82                        <?php else: ?>
     83                            <span style="color: #dc3232;">✗ <?php esc_html_e('Missing', 'visualwebs-ml'); ?></span>
     84                        <?php endif; ?>
     85                    </td>
     86                </tr>
     87                <tr>
     88                    <td><strong><?php esc_html_e('n8n Workflows', 'visualwebs-ml'); ?></strong></td>
     89                    <td>
     90                        <?php if ($visualwebs_ml_workflows_enabled): ?>
     91                            <span style="color: #46b450;">✓ <?php esc_html_e('Enabled', 'visualwebs-ml'); ?></span>
     92                        <?php else: ?>
     93                            <span style="color: #999;">✗ <?php esc_html_e('Disabled', 'visualwebs-ml'); ?></span>
     94                        <?php endif; ?>
     95                    </td>
     96                </tr>
     97                <tr>
     98                    <td><strong><?php esc_html_e('SmartPricing AI', 'visualwebs-ml'); ?></strong></td>
     99                    <td>
     100                        <?php if ($visualwebs_ml_helper->getIsDynamicPricingEnabled()): ?>
     101                            <span style="color: #46b450;">✓ <?php esc_html_e('Enabled', 'visualwebs-ml'); ?></span>
     102                        <?php else: ?>
     103                            <span style="color: #999;">✗ <?php esc_html_e('Disabled', 'visualwebs-ml'); ?></span>
     104                        <?php endif; ?>
     105                    </td>
     106                </tr>
     107                <tr>
     108                    <td><strong><?php esc_html_e('Chatbot Widget', 'visualwebs-ml'); ?></strong></td>
     109                    <td>
     110                        <?php if ($visualwebs_ml_helper->getConfig('chatbot_enabled', false)): ?>
     111                            <span style="color: #46b450;">✓ <?php esc_html_e('Enabled', 'visualwebs-ml'); ?></span>
     112                        <?php else: ?>
     113                            <span style="color: #999;">✗ <?php esc_html_e('Disabled', 'visualwebs-ml'); ?></span>
     114                        <?php endif; ?>
     115                    </td>
     116                </tr>
     117            </tbody>
     118        </table>
     119    </div>
     120
     121    <div class="card">
     122        <h2><?php esc_html_e('Feed Synchronization', 'visualwebs-ml'); ?></h2>
     123        <p><?php esc_html_e('Automatic feed generation is configured:', 'visualwebs-ml'); ?></p>
     124        <ul>
     125            <li><strong><?php esc_html_e('Product Feed', 'visualwebs-ml'); ?>:</strong> <?php esc_html_e('Hourly', 'visualwebs-ml'); ?></li>
     126            <li><strong><?php esc_html_e('Page Feed', 'visualwebs-ml'); ?>:</strong> <?php esc_html_e('Daily at 2 AM', 'visualwebs-ml'); ?></li>
     127            <li><strong><?php esc_html_e('Sales Feed', 'visualwebs-ml'); ?>:</strong> <?php esc_html_e('Daily at 2 AM', 'visualwebs-ml'); ?></li>
     128        </ul>
     129        <p>
     130            <a href="<?php echo esc_url(admin_url('admin.php?page=visualwebs-ml-feeds')); ?>" class="button">
     131                <?php esc_html_e('Manage Feeds', 'visualwebs-ml'); ?>
     132            </a>
     133        </p>
     134    </div>
     135
     136    <div class="card">
     137        <h2><?php esc_html_e('Quick Links', 'visualwebs-ml'); ?></h2>
     138        <p>
     139            <a href="<?php echo esc_url(admin_url('admin.php?page=visualwebs-ml-manage-options')); ?>" class="button">
     140                <?php esc_html_e('Settings', 'visualwebs-ml'); ?>
     141            </a>
     142            <a href="<?php echo esc_url(admin_url('admin.php?page=visualwebs-ml-feeds')); ?>" class="button">
     143                <?php esc_html_e('Feed Generation', 'visualwebs-ml'); ?>
     144            </a>
     145            <a href="https://visualwebs.eu/docs/plugin/ai-cloud-suite/" target="_blank" class="button">
     146                <?php esc_html_e('Documentation', 'visualwebs-ml'); ?>
     147            </a>
     148        </p>
     149    </div>
     150</div>
  • visualwebs-ml/trunk/vendor/autoload.php

    r3303071 r3476789  
    1515        }
    1616    }
    17     trigger_error(
    18         $err,
    19         E_USER_ERROR
    20     );
     17    throw new RuntimeException($err);
    2118}
    2219
  • visualwebs-ml/trunk/vendor/composer/autoload_static.php

    r3319473 r3476789  
    1212
    1313    public static $prefixLengthsPsr4 = array (
    14         'V' => 
     14        'V' =>
    1515        array (
    1616            'Visualwebs\\ML\\' => 14,
    1717        ),
    18         'S' => 
     18        'S' =>
    1919        array (
    2020            'Symfony\\Polyfill\\Mbstring\\' => 26,
    2121        ),
    22         'P' => 
     22        'P' =>
    2323        array (
    2424            'PhpOffice\\PhpWord\\' => 18,
     
    2828
    2929    public static $prefixDirsPsr4 = array (
    30         'Visualwebs\\ML\\' => 
     30        'Visualwebs\\ML\\' =>
    3131        array (
    3232            0 => __DIR__ . '/..' . '/visualwebs-ml',
    3333        ),
    34         'Symfony\\Polyfill\\Mbstring\\' => 
     34        'Symfony\\Polyfill\\Mbstring\\' =>
    3535        array (
    3636            0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
    3737        ),
    38         'PhpOffice\\PhpWord\\' => 
     38        'PhpOffice\\PhpWord\\' =>
    3939        array (
    4040            0 => __DIR__ . '/..' . '/phpoffice/phpword/src/PhpWord',
    4141        ),
    42         'PhpOffice\\Math\\' => 
     42        'PhpOffice\\Math\\' =>
    4343        array (
    4444            0 => __DIR__ . '/..' . '/phpoffice/math/src/Math',
     
    4747
    4848    public static $prefixesPsr0 = array (
    49         'S' => 
     49        'S' =>
    5050        array (
    51             'Smalot\\PdfParser\\' => 
     51            'Smalot\\PdfParser\\' =>
    5252            array (
    5353                0 => __DIR__ . '/..' . '/smalot/pdfparser/src',
  • visualwebs-ml/trunk/vendor/composer/platform_check.php

    r3319484 r3476789  
    2020        }
    2121    }
    22     trigger_error(
    23         'Composer detected issues in your platform: ' . implode(' ', $issues),
    24         E_USER_ERROR
     22    throw new \RuntimeException(
     23        'Composer detected issues in your platform: ' . implode(' ', $issues)
    2524    );
    2625}
  • visualwebs-ml/trunk/vendor/visualwebs-ml/Activator.php

    r3325281 r3476789  
    33namespace Visualwebs\ML;
    44
     5/**
     6 * Plugin Activator
     7 *
     8 * @package Visualwebs\ML
     9 * @since 5.5.0
     10 *
     11 * Note: Database tables removed in v5.5.0 - SaaS architecture migration.
     12 * All data is now managed centrally by the SaaS platform.
     13 * Configuration is stored in wp_options table.
     14 */
    515class Activator
    616{
     17    /**
     18     * Plugin activation hook
     19     *
     20     * @since 5.5.0 No database tables created - SaaS manages all data
     21     */
    722    public static function activate()
    823    {
    9         global $wpdb;
    10 
    11         $sematic_queue_table_name = $wpdb->prefix . 'visualwebs_ml_semantic_queue';
    12         $charset_collate = $wpdb->get_charset_collate();
    13 
    14         $sql1 = "CREATE TABLE $sematic_queue_table_name (
    15             job_id int(11) NOT NULL AUTO_INCREMENT COMMENT 'Job Id',
    16             job_object_entity_id int(11) NOT NULL COMMENT 'Job Object Entity Id',
    17             job_object_type varchar(50) NOT NULL COMMENT 'Job Object Type',
    18             job_object_name varchar(255) DEFAULT NULL COMMENT 'Job Object Name',
    19             job_object_ref varchar(255) DEFAULT NULL COMMENT 'Job Object Ref',
    20             job_object_content text COMMENT 'Job Object Content',
    21             job_object_content_lock smallint(6) NOT NULL DEFAULT '0' COMMENT 'Job Object Content Lock',
    22             job_status smallint(6) NOT NULL DEFAULT '0' COMMENT 'Status',
    23             job_sync_status smallint(6) NOT NULL DEFAULT '0' COMMENT 'Sync Status',
    24             created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Date and time of job creation',
    25             updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Date and time of job update',
    26             job_errors varchar(255) DEFAULT NULL COMMENT 'Job Errors',
    27             job_object_files_content text COMMENT 'Job Object Files Content',
    28             PRIMARY KEY  (job_id),
    29             UNIQUE KEY ML_SEMANTIC_QUEUE_JOB_OBJECT_TYPE_JOB_OBJECT_ENTITY_ID (job_object_type, job_object_entity_id)
    30         ) $charset_collate;";
    31 
    32         $dynamic_pricing_jobs_table_name = $wpdb->prefix . 'visualwebs_ml_dynamic_pricing_jobs';
    33 
    34         $sql2 = "CREATE TABLE $dynamic_pricing_jobs_table_name (
    35             job_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    36             job_hash VARCHAR(128) NOT NULL,
    37             job_type VARCHAR(32) NOT NULL DEFAULT 'train',
    38             job_status VARCHAR(20) NOT NULL DEFAULT 'pending',
    39             created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Date and time of job creation',
    40             updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Date and time of job update',
    41             job_data LONGTEXT,
    42             job_response LONGTEXT,
    43             PRIMARY KEY  (job_id),
    44             UNIQUE KEY job_hash (job_hash)
    45         ) $charset_collate;";
    46 
    47         require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    48         dbDelta($sql1);
    49         dbDelta($sql2);
     24        // No database tables needed - SaaS architecture
     25        // Configuration is stored in wp_options via Redux Framework
     26        // Feeds are generated as JSON files in wp-content/uploads/visualwebs-ai/
     27       
     28        // Ensure upload directory exists
     29        $upload_dir = wp_upload_dir();
     30        $visualwebs_dir = $upload_dir['basedir'] . '/visualwebs-ai';
     31       
     32        if (!file_exists($visualwebs_dir)) {
     33            wp_mkdir_p($visualwebs_dir);
     34        }
     35       
     36        // Set default options if not present
     37        $options = get_option('visualwebs_ml_options');
     38       
     39        if ($options === false) {
     40            $default_options = array(
     41                'enabled' => false,
     42                'api_enabled' => false,
     43                'chatbot_enabled' => false,
     44                'enable_workflows' => false,
     45                'anonymize_payload' => false,
     46            );
     47           
     48            add_option('visualwebs_ml_options', $default_options);
     49        }
     50       
     51        // Flush rewrite rules for REST API endpoints
     52        flush_rewrite_rules();
    5053    }
    5154}
  • visualwebs-ml/trunk/vendor/visualwebs-ml/Admin.php

    r3319473 r3476789  
    2525    {
    2626        add_action('admin_menu', [__CLASS__, 'add_admin_menu']);
    27         add_action('admin_footer', [__CLASS__, 'widget_page']);
     27        add_action('admin_footer', [__CLASS__, 'render_chatbot_widget']);
    2828        add_action('wp_ajax_visualwebs_ml_search_entity', [__CLASS__, 'visualwebs_ml_search_entity']);
    2929        if (self::get_helper()->getIsDynamicPricingEnabled()) {
     
    6262        add_submenu_page(
    6363            'visualwebs-ml',
    64             'Semantic Search',
    65             'Semantic Search',
    66             'manage_options',
    67             'visualwebs-ml-semantic-queue',
    68             [__CLASS__, 'semantic_search_queue_page']
    69         );
    70 
    71         add_submenu_page(
    72             null,
    73             'Add New Search Item',
    74             'Add New Search Item',
    75             'manage_options',
    76             'visualwebs-ml-semantic-add',
    77             [__CLASS__, 'semantic_search_add_page']
    78         );
    79 
    80         add_submenu_page(
    81             null,
    82             'Edit Search Item',
    83             'Edit Search Item',
    84             'manage_options',
    85             'visualwebs-ml-semantic-edit',
    86             [__CLASS__, 'semantic_search_edit_page']
    87         );
    88 
    89         add_submenu_page(
    90             'visualwebs-ml',
    91             'SmartPricing AI',
    92             'SmartPricing AI',
    93             'manage_options',
    94             'visualwebs-ml-dynamic-pricing-queue',
    95             [__CLASS__, 'dynamic_pricing_queue_page']
    96         );
    97 
    98         add_submenu_page(
    99             null,
    100             'View Job Item',
    101             'View Job Item',
    102             'manage_options',
    103             'visualwebs-ml-dynamic-pricing-view',
    104             [__CLASS__, 'dynamic_pricing_queue_view_page']
    105         );
    106 
    107         add_submenu_page(
    108             null,
    109             'Train',
    110             'Train',
    111             'manage_options',
    112             'visualwebs-ml-dynamic-pricing-train',
    113             [__CLASS__, 'dynamic_pricing_train_page']
    114         );
    115 
    116         add_submenu_page(
    117             null,
    118             'Predict',
    119             'Predict',
    120             'manage_options',
    121             'visualwebs-ml-dynamic-pricing-predict',
    122             [__CLASS__, 'dynamic_pricing_predict_page']
    123         );
    124 
    125         add_submenu_page(
    126             'visualwebs-ml',
    12764            'Settings',
    12865            'Settings',
     
    13067            'visualwebs-ml-manage-options',
    13168            [__CLASS__, 'settings_page']
     69        );
     70
     71        add_submenu_page(
     72            'visualwebs-ml',
     73            'Feed Generation',
     74            'Feed Generation',
     75            'manage_options',
     76            'visualwebs-ml-feeds',
     77            [__CLASS__, 'feeds_page']
    13278        );
    13379
     
    264210    public static function dashboard_page()
    265211    {
    266         include(plugin_dir_path(__FILE__) . '../../templates/ml-widgets.php');
    267212        include(plugin_dir_path(__FILE__) . '../../templates/ml-dashboard.php');
    268213    }
    269214
    270     public static function widget_page()
    271     {
    272         include(plugin_dir_path(__FILE__) . '../../templates/ml-widgets.php');
    273     }
    274 
    275     public static function semantic_search_queue_page()
    276     {
    277         include plugin_dir_path(__FILE__) . '../../templates/semantic-search-queue.php';
    278     }
    279 
    280     public static function semantic_search_add_page()
    281     {
    282         include plugin_dir_path(__FILE__) . '../../templates/semantic-search-add.php';
    283     }
    284 
    285     public static function semantic_search_edit_page()
    286     {
    287         include plugin_dir_path(__FILE__) . '../../templates/semantic-search-edit.php';
    288     }
    289 
    290     public static function dynamic_pricing_queue_page()
    291     {
    292         include plugin_dir_path(__FILE__) . '../../templates/dynamic-pricing-queue.php';
    293     }
    294 
    295     public static function dynamic_pricing_queue_view_page()
    296     {
    297         include plugin_dir_path(__FILE__) . '../../templates/dynamic-pricing-view.php';
    298     }
    299 
    300     public static function dynamic_pricing_train_page()
    301     {
    302         include plugin_dir_path(__FILE__) . '../../templates/dynamic-pricing-train.php';
    303     }
    304 
    305     public static function dynamic_pricing_predict_page()
    306     {
    307         include plugin_dir_path(__FILE__) . '../../templates/dynamic-pricing-predict.php';
     215    /**
     216     * Render chatbot widget in admin footer.
     217     *
     218     * @since 5.5.0
     219     */
     220    public static function render_chatbot_widget()
     221    {
     222        require plugin_dir_path(__FILE__) . '../../templates/n8n-chatbot-widget.php';
     223    }
     224
     225    public static function settings_page()
     226    {
     227        // Redux Framework handles this page
     228    }
     229
     230    public static function feeds_page()
     231    {
     232        require_once plugin_dir_path(__FILE__) . '../../includes/feeds/class-product-feed-generator.php';
     233        require_once plugin_dir_path(__FILE__) . '../../includes/feeds/class-page-feed-generator.php';
     234        require_once plugin_dir_path(__FILE__) . '../../includes/feeds/class-sales-feed-generator.php';
     235        require_once plugin_dir_path(__FILE__) . '../../includes/feeds/class-insights-feed-generator.php';
     236
     237        $message = '';
     238        $messageType = '';
     239
     240        if (isset($_POST['generate_products']) && check_admin_referer('visualwebs_ml_feeds', 'visualwebs_ml_feeds_nonce')) {
     241            $generator = new \VisualwebsML_ProductFeedGenerator();
     242            $url = $generator->generate();
     243            if ($url) {
     244                $message = sprintf(__('Product feed generated successfully: %s', 'visualwebs-ml'), $url);
     245                $messageType = 'success';
     246            } else {
     247                $message = __('Failed to generate product feed', 'visualwebs-ml');
     248                $messageType = 'error';
     249            }
     250        }
     251
     252        if (isset($_POST['generate_pages']) && check_admin_referer('visualwebs_ml_feeds', 'visualwebs_ml_feeds_nonce')) {
     253            $generator = new \VisualwebsML_PageFeedGenerator();
     254            $url = $generator->generate();
     255            if ($url) {
     256                $message = sprintf(__('Page feed generated successfully: %s', 'visualwebs-ml'), $url);
     257                $messageType = 'success';
     258            } else {
     259                $message = __('Failed to generate page feed', 'visualwebs-ml');
     260                $messageType = 'error';
     261            }
     262        }
     263
     264        if (isset($_POST['generate_sales']) && check_admin_referer('visualwebs_ml_feeds', 'visualwebs_ml_feeds_nonce')) {
     265            $generator = new \VisualwebsML_SalesFeedGenerator();
     266            $url = $generator->generate();
     267            if ($url) {
     268                $message = sprintf(__('Sales feed generated successfully: %s', 'visualwebs-ml'), $url);
     269                $messageType = 'success';
     270            } else {
     271                $message = __('Failed to generate sales feed', 'visualwebs-ml');
     272                $messageType = 'error';
     273            }
     274        }
     275
     276        if (isset($_POST['generate_insights']) && check_admin_referer('visualwebs_ml_feeds', 'visualwebs_ml_feeds_nonce')) {
     277            $generator = new \VisualwebsML_InsightsFeedGenerator();
     278            $url = $generator->generate();
     279            if ($url) {
     280                $message = sprintf(__('Insights feed generated successfully: %s', 'visualwebs-ml'), $url);
     281                $messageType = 'success';
     282            } else {
     283                $message = __('Failed to generate insights feed', 'visualwebs-ml');
     284                $messageType = 'error';
     285            }
     286        }
     287
     288        ?>
     289        <div class="wrap">
     290            <h1><?php esc_html_e('Feed Generation', 'visualwebs-ml'); ?></h1>
     291           
     292            <?php if ($message): ?>
     293                <div class="notice notice-<?php echo esc_attr($messageType); ?> is-dismissible">
     294                    <p><?php echo esc_html($message); ?></p>
     295                </div>
     296            <?php endif; ?>
     297
     298            <p><?php esc_html_e('Manually trigger feed generation. Feeds are also generated automatically via cron jobs.', 'visualwebs-ml'); ?></p>
     299
     300            <div class="card">
     301                <h2><?php esc_html_e('Product Feed', 'visualwebs-ml'); ?></h2>
     302                <p><?php esc_html_e('Generates JSON feed of all products for semantic search synchronization. Auto-generated hourly.', 'visualwebs-ml'); ?></p>
     303                <form method="post">
     304                    <?php wp_nonce_field('visualwebs_ml_feeds', 'visualwebs_ml_feeds_nonce'); ?>
     305                    <button type="submit" name="generate_products" class="button button-primary">
     306                        <?php esc_html_e('Generate Product Feed', 'visualwebs-ml'); ?>
     307                    </button>
     308                </form>
     309            </div>
     310
     311            <div class="card">
     312                <h2><?php esc_html_e('Page Feed', 'visualwebs-ml'); ?></h2>
     313                <p><?php esc_html_e('Generates JSON feed of all pages and posts for content search. Auto-generated daily at 2 AM.', 'visualwebs-ml'); ?></p>
     314                <form method="post">
     315                    <?php wp_nonce_field('visualwebs_ml_feeds', 'visualwebs_ml_feeds_nonce'); ?>
     316                    <button type="submit" name="generate_pages" class="button button-primary">
     317                        <?php esc_html_e('Generate Page Feed', 'visualwebs-ml'); ?>
     318                    </button>
     319                </form>
     320            </div>
     321
     322            <div class="card">
     323                <h2><?php esc_html_e('Sales Feed', 'visualwebs-ml'); ?></h2>
     324                <p><?php esc_html_e('Generates JSON feed of sales data for SmartPricing AI training. Auto-generated daily at 2 AM.', 'visualwebs-ml'); ?></p>
     325                <form method="post">
     326                    <?php wp_nonce_field('visualwebs_ml_feeds', 'visualwebs_ml_feeds_nonce'); ?>
     327                    <button type="submit" name="generate_sales" class="button button-primary">
     328                        <?php esc_html_e('Generate Sales Feed', 'visualwebs-ml'); ?>
     329                    </button>
     330                </form>
     331            </div>
     332
     333            <div class="card">
     334                <h2><?php esc_html_e('Insights Feed', 'visualwebs-ml'); ?></h2>
     335                <p><?php esc_html_e('Generates JSON feed of dashboard insights (sales, bestsellers, reviews, registrations) for SaaS widgets. Auto-generated daily at 3 AM.', 'visualwebs-ml'); ?></p>
     336                <form method="post">
     337                    <?php wp_nonce_field('visualwebs_ml_feeds', 'visualwebs_ml_feeds_nonce'); ?>
     338                    <button type="submit" name="generate_insights" class="button button-primary">
     339                        <?php esc_html_e('Generate Insights Feed', 'visualwebs-ml'); ?>
     340                    </button>
     341                </form>
     342            </div>
     343
     344            <div class="card">
     345                <h2><?php esc_html_e('Feed Files', 'visualwebs-ml'); ?></h2>
     346                <p><?php esc_html_e('Generated feeds are stored in:', 'visualwebs-ml'); ?> <code><?php echo esc_html(wp_upload_dir()['baseurl'] . '/visualwebs-ml/'); ?></code></p>
     347                <ul>
     348                    <li><strong>product_feed.json</strong> - Product catalog</li>
     349                    <li><strong>page_feed.json</strong> - Pages and posts</li>
     350                    <li><strong>sales_feed.json</strong> - Sales data for SmartPricing</li>
     351                    <li><strong>insights_feed.json</strong> - Dashboard insights and statistics</li>
     352                </ul>
     353            </div>
     354        </div>
     355        <?php
    308356    }
    309357
  • visualwebs-ml/trunk/vendor/visualwebs-ml/Block/Adminhtml/Dashboard.php

    r3337157 r3476789  
    320320     * Retrieve the existing widgets for the dashboard.
    321321     *
    322      * @return array An array of user widgets.
     322     * @return array An array of user widgets (empty - config removed).
    323323     */
    324324    public function getUserWidgets()
    325325    {
    326         return $this->helper->getUserWidgets();
     326        // User widgets config removed (now managed in SaaS)
     327        return [];
    327328    }
    328329
  • visualwebs-ml/trunk/vendor/visualwebs-ml/Block/ChatWidget.php

    r3337157 r3476789  
    4040
    4141    /**
     42     * Retrieve the URL for the admin chatbot proxy API.
     43     *
     44     * @return string
     45     */
     46    public function getAdminChatApiUrl()
     47    {
     48        return $this->helper->getAdminChatApiUrl();
     49    }
     50
     51    /**
    4252     * Check if the chatbot is enabled.
    4353     *
     
    4757    {
    4858        return $this->helper->getIsChatbotEnabled();
     59    }
     60
     61    /**
     62     * Check if the admin chatbot is enabled.
     63     *
     64     * @return bool
     65     */
     66    public function getIsAdminChatbotEnabled()
     67    {
     68        return $this->helper->getIsAdminChatbotEnabled();
    4969    }
    5070
     
    235255        return $this->helper->getCurrentFrontendLanguageShort();
    236256    }
     257
     258    /**
     259     * Get chatbot namespace.
     260     *
     261     * @return string
     262     */
     263    public function getChatbotNamespace()
     264    {
     265        return $this->helper->getChatbotNamespace();
     266    }
     267
     268    /**
     269     * Get chatbot store ID.
     270     *
     271     * @return string
     272     */
     273    public function getChatbotStoreId()
     274    {
     275        return $this->helper->getChatbotStoreId();
     276    }
     277
     278    /**
     279     * Get "Powered by" text.
     280     *
     281     * @return string
     282     */
     283    public function getChatbotPoweredByText()
     284    {
     285        return $this->helper->getChatbotPoweredByText();
     286    }
     287
     288    /**
     289     * Get "Powered by" link.
     290     *
     291     * @return string
     292     */
     293    public function getChatbotPoweredByLink()
     294    {
     295        return $this->helper->getChatbotPoweredByLink();
     296    }
     297
     298    /**
     299     * Get chatbot assets base URL.
     300     *
     301     * @return string
     302     */
     303    public function getChatbotAssetsBaseUrl()
     304    {
     305        return $this->helper->getChatbotAssetsBaseUrl();
     306    }
     307
     308    /**
     309     * Get conversation unique session ID.
     310     *
     311     * @return string
     312     */
     313    public function getConversationUniqueSessionId()
     314    {
     315        return $this->helper->getConversationUniqueSessionId();
     316    }
    237317}
  • visualwebs-ml/trunk/vendor/visualwebs-ml/Helper/Chatbot.php

    r3319473 r3476789  
    1010class Chatbot extends \Visualwebs\ML\Helper\Data
    1111{
     12    private const SAAS_API_BASE_URL = 'https://saas.visualwebs.eu/api/v1';
     13    private const SAAS_CHATBOT_CONFIG_PATH = '/chatbot/public/config/';
     14    private const SAAS_CHATBOT_CACHE_TTL = DAY_IN_SECONDS;
     15    private const SAAS_CHATBOT_ASSETS_BASE_URL = 'https://saas.visualwebs.eu/assets/chatbot/v1';
     16
    1217    public const CHATBOT_DEFAULT_HEADER_TITLE = 'Support';
    1318    public const CHATBOT_DEFAULT_OFFLINE_MESSAGE = 'Our support system is OFF now, please try later.';
     
    2328        $isChatbotEnabled = $this->getConfig('chatbot_enabled', false);
    2429        $isVisualwebsMlServicesEnabled = $this->getIsVisualwebsMlServicesEnabled();
    25         return $isChatbotEnabled && $isVisualwebsMlServicesEnabled;
     30        $chatApiUrl = $this->getChatApiUrl();
     31
     32        return $isChatbotEnabled && $isVisualwebsMlServicesEnabled && !empty($chatApiUrl);
     33    }
     34
     35    /**
     36     * Check if the admin chatbot is enabled.
     37     *
     38     * @return bool
     39     */
     40    public function getIsAdminChatbotEnabled()
     41    {
     42        $isAdminEnabled = $this->getConfig('admin_enabled', false);
     43        $isVisualwebsMlServicesEnabled = $this->getIsVisualwebsMlServicesEnabled();
     44        $chatApiUrl = $this->getChatApiUrl();
     45
     46        return $isAdminEnabled && $isVisualwebsMlServicesEnabled && !empty($chatApiUrl);
    2647    }
    2748
     
    117138    public function getChatbotMainBackgroundColor()
    118139    {
    119         $backgroundColor = $this->getConfig('chatbot_main_background_color', true);
    120         return $backgroundColor ?: "#000";
     140        $saasColor = $this->getSaasWidgetConfigValue('color');
     141        return $saasColor ? (string)$saasColor : "#000";
    121142    }
    122143
     
    128149    public function getChatbotDisclaimerUrl()
    129150    {
    130         $disclaimerUrl = $this->getConfig('chatbot_disclaimer_url', '');
    131         return $disclaimerUrl ? get_page_link(get_page_by_path($disclaimerUrl)) : get_page_link(get_page_by_path('about-us'));
     151        $saasDisclaimer = $this->getSaasWidgetConfigValue('disclaimer_url');
     152        return $saasDisclaimer ? (string)$saasDisclaimer : '';
    132153    }
    133154
     
    139160    public function getChatbotContext()
    140161    {
    141         $chatBotContext = $this->getChatbotContextValue();
    142         return $chatBotContext ? trim($chatBotContext) : __('Act as E-commerce expert', 'visualwebs-ml');
    143     }
    144 
    145     /**
    146      * Retrieve the context value for the chatbot in config.
    147      *
    148      * @return mixed The user defined context value for the chatbot.
    149      */
    150     public function getChatbotContextValue()
    151     {
    152         $chatBotContext = $this->getConfig('chatbot_context', '');
    153         return $chatBotContext;
     162        $saasPrompt = $this->getSaasWidgetConfigValue('prompt');
     163        return $saasPrompt ? trim((string)$saasPrompt) : __('Act as E-commerce expert', 'visualwebs-ml');
    154164    }
    155165
     
    161171    public function getChatbotHeaderTitle()
    162172    {
    163         $chatBotHeadTitle = $this->getConfig('chatbot_header_title', '');
     173        $saasTitle = $this->getSaasWidgetConfigValue('title');
    164174        $defaultChatBotHeadTitle = self::CHATBOT_DEFAULT_HEADER_TITLE;
    165         return $chatBotHeadTitle ? trim($chatBotHeadTitle) : $defaultChatBotHeadTitle;
     175        return $saasTitle ? trim((string)$saasTitle) : __($defaultChatBotHeadTitle, 'visualwebs-ml');
    166176    }
    167177
     
    173183    public function getChatbotOfflineMessage()
    174184    {
    175         $chatBotOfflineMessage = $this->getConfig('chatbot_offline_message', '');
     185        $saasOfflineMessage = $this->getSaasWidgetConfigValue('offline_message');
    176186        $defaultChatBotOfflineMessage = self::CHATBOT_DEFAULT_OFFLINE_MESSAGE;
    177         return $chatBotOfflineMessage ? trim($chatBotOfflineMessage) : $defaultChatBotOfflineMessage;
     187        return $saasOfflineMessage ? trim((string)$saasOfflineMessage) : __($defaultChatBotOfflineMessage, 'visualwebs-ml');
    178188    }
    179189
     
    185195    public function getChatbotLogoUrl()
    186196    {
    187         if ($imagePath = $this->getConfig('chatbot_logo')) {
    188             if (isset($imagePath['url'])) {
    189                 return $imagePath['url'];
    190             }
    191         }
    192 
    193         return '';
     197        $saasLogo = $this->getSaasWidgetConfigValue('logo');
     198        return $saasLogo ? (string)$saasLogo : '';
     199    }
     200
     201    /**
     202     * Retrieve SaaS chatbot webhook URL.
     203     *
     204     * @return string
     205     */
     206    public function getChatApiUrl()
     207    {
     208        $webhookUrl = $this->getSaasChatbotConfigValue('n8n_webhook_url');
     209        return $webhookUrl ? esc_url_raw((string) $webhookUrl) : '';
     210    }
     211
     212    /**
     213     * Retrieve admin chatbot proxy endpoint (secured local REST endpoint).
     214     *
     215     * @return string
     216     */
     217    public function getAdminChatApiUrl()
     218    {
     219        $nonce = wp_create_nonce('visualwebs_ml_admin_chat');
     220        return esc_url_raw(rest_url('visualwebs-ml/v1/chat-api/admin-call?_vwml_nonce=' . rawurlencode($nonce)));
    194221    }
    195222
     
    225252    }
    226253
     254    /**
     255     * Returns raw SaaS chatbot config payload (cached).
     256     *
     257     * @return array|null
     258     */
     259    private function getSaasChatbotConfig()
     260    {
     261        $storeId = $this->getStoreId();
     262        if (empty($storeId)) {
     263            return null;
     264        }
     265
     266        $cacheKey = 'vwml_chatbot_config_' . md5($storeId);
     267        $cached = get_transient($cacheKey);
     268        if (is_array($cached)) {
     269            return $cached;
     270        }
     271
     272        $url = self::SAAS_API_BASE_URL . self::SAAS_CHATBOT_CONFIG_PATH . rawurlencode($storeId);
     273        $headers = array('Accept' => 'application/json');
     274        $apiKey = $this->getApiKey();
     275        if (!empty($apiKey)) {
     276            $headers['x-api-key'] = $apiKey;
     277        }
     278
     279        $response = wp_remote_get(
     280            $url,
     281            array(
     282                'headers' => $headers,
     283                'timeout' => 8,
     284            )
     285        );
     286
     287        if (is_wp_error($response)) {
     288            $this->debug('[Visualwebs ML] Failed to fetch SaaS chatbot config: ' . $response->get_error_message());
     289            return null;
     290        }
     291
     292        $status = (int) wp_remote_retrieve_response_code($response);
     293        if ($status !== 200) {
     294            $this->debug('[Visualwebs ML] Failed to fetch SaaS chatbot config. HTTP status: ' . $status);
     295            return null;
     296        }
     297
     298        $body = wp_remote_retrieve_body($response);
     299        $decoded = json_decode($body, true);
     300        if (!is_array($decoded)) {
     301            return null;
     302        }
     303
     304        set_transient($cacheKey, $decoded, self::SAAS_CHATBOT_CACHE_TTL);
     305
     306        return $decoded;
     307    }
     308
     309    /**
     310     * Returns a top-level key from SaaS chatbot config payload.
     311     *
     312     * @param string $key
     313     * @return mixed|null
     314     */
     315    private function getSaasChatbotConfigValue($key)
     316    {
     317        $response = $this->getSaasChatbotConfig();
     318        if (!$response || !isset($response['config']) || !is_array($response['config'])) {
     319            return null;
     320        }
     321
     322        return array_key_exists($key, $response['config']) ? $response['config'][$key] : null;
     323    }
     324
     325    /**
     326     * Returns a widget_config key from SaaS config.
     327     *
     328     * @param string $key
     329     * @return mixed|null
     330     */
     331    private function getSaasWidgetConfigValue($key)
     332    {
     333        $response = $this->getSaasChatbotConfig();
     334        if (!$response || !isset($response['config']) || !is_array($response['config'])) {
     335            return null;
     336        }
     337
     338        $config = $response['config'];
     339        if (!isset($config['widget_config'])) {
     340            return null;
     341        }
     342
     343        $widgetConfig = $config['widget_config'];
     344        if (is_string($widgetConfig)) {
     345            $decoded = json_decode($widgetConfig, true);
     346            $widgetConfig = is_array($decoded) ? $decoded : array();
     347        }
     348
     349        return isset($widgetConfig[$key]) ? $widgetConfig[$key] : null;
     350    }
     351
     352    /**
     353     * Get chatbot namespace (for multi-bot identification).
     354     *
     355     * @return string
     356     */
     357    public function getChatbotNamespace()
     358    {
     359        return $this->getSaasChatbotConfigValue('namespace') ?: 'default';
     360    }
     361
     362    /**
     363     * Get chatbot store ID.
     364     *
     365     * @return string
     366     */
     367    public function getChatbotStoreId()
     368    {
     369        return $this->getStoreId();
     370    }
     371
     372    /**
     373     * Get "Powered by" text for chatbot footer.
     374     *
     375     * @return string
     376     */
     377    public function getChatbotPoweredByText()
     378    {
     379        return $this->getSaasChatbotConfigValue('powered_by_text') ?: 'Powered by Visualwebs AI';
     380    }
     381
     382    /**
     383     * Get "Powered by" link for chatbot footer.
     384     *
     385     * @return string
     386     */
     387    public function getChatbotPoweredByLink()
     388    {
     389        return $this->getSaasChatbotConfigValue('powered_by_link') ?: 'https://visualwebs.eu';
     390    }
     391
     392    /**
     393     * Get chatbot assets base URL (where n8n-chat-widget.js is hosted).
     394     *
     395     * @return string
     396     */
     397    public function getChatbotAssetsBaseUrl()
     398    {
     399        return self::SAAS_CHATBOT_ASSETS_BASE_URL;
     400    }
     401
    227402}
  • visualwebs-ml/trunk/vendor/visualwebs-ml/Helper/ChatbotApiCall.php

    r3322994 r3476789  
    1010use Visualwebs\ML\Helper\Visualwebs as ApiHelper;
    1111use Visualwebs\ML\Helper\Chatbot as ChatbotHelper;
    12 use Visualwebs\ML\Helper\OpenAi as OpenAiHelper;
    13 use Visualwebs\ML\Helper\Pinecone as PineconeHelper;
     12use Visualwebs\ML\Helper\Data as DataHelper;
    1413
    1514class ChatbotApiCall
     
    2625
    2726    /**
    28      * @var \Visualwebs\ML\Helper\OpenAiHelper
    29      */
    30     protected $openAiHelper;
    31 
    32     /**
    33      * @var \Visualwebs\ML\Helper\PineconeHelper
    34      */
    35     protected $pineconeHelper;
     27     * @var \Visualwebs\ML\Helper\DataHelper
     28     */
     29    protected $dataHelper;
    3630
    3731    /**
     
    4943        $this->apiHelper = new ApiHelper();
    5044        $this->chatbotHelper = new ChatbotHelper();
    51         $this->openAiHelper = new OpenAiHelper();
    52         $this->pineconeHelper = new PineconeHelper();
     45        $this->dataHelper = new DataHelper();
    5346        $this->debugEnabled = $this->chatbotHelper->getIsDebugChatbotEnabled();
    5447    }
     
    6255     * @return void
    6356     */
    64     public function execute()
    65     {
    66         $frontendBaseUrl = $this->chatbotHelper->getFrontendBaseUrl();
    67         $context =  $this->chatbotHelper->getChatbotContextValue();
    68         $input = json_decode(file_get_contents('php://input'), true);
     57    public function execute($input = null)
     58    {
     59        $input = is_array($input) ? $input : json_decode(file_get_contents('php://input'), true);
     60        if (!is_array($input)) {
     61            $input = array();
     62        }
    6963   
    7064        $message = isset($input['message']) ? sanitize_text_field(wp_unslash($input['message'])) : '';
     
    7468            : [];
    7569
    76         // Important, we retrieve here because in frontend is cached
    77         $uniqueSessionId = $this->chatbotHelper->getConversationUniqueSessionId();
    78         $openAiApiKey = $this->openAiHelper->getApiKey();
    79         $offMessage = $this->chatbotHelper->getChatbotOfflineMessage();
    80         $useCustomerApiKeysEnabled = $this->chatbotHelper->getUseCustomerApiKeysEnabled();
    81         $domainNamespace = $this->chatbotHelper->getDomainNamespace();
    82 
    83         if ($useCustomerApiKeysEnabled) {
    84             $args = [
    85                 'openai_api_key' => $openAiApiKey,
    86                 'session_id' => $uniqueSessionId,
    87                 'is_single_prompt' => !$isChat
    88             ];
    89         } else {
    90             $args = [
    91                 'session_id' => $uniqueSessionId,
    92                 'is_single_prompt' => !$isChat
    93             ];
    94         }
    95 
    96         if ($isChat) {
    97 
    98             $args['instructions'] = $context;
    99             $args['catalog_search_url'] = $frontendBaseUrl . "?s=";
    100             $args['tools'] = ["get_order_status", "get_product_stock"];
    101             $args['tools_endpoint'] = $this->chatbotHelper->getCallbackApiUrl();
    102             $args['current_page_info'] = $currentPageInfo;
    103             if ($offMessage && trim($offMessage)) {
    104                 $args['offline_message'] =  $offMessage;
    105             }
    106 
    107             if ($this->chatbotHelper->getIsSemanticSearchEnabled()) {
    108 
    109                 // We offer Pinecone only at this moment
    110 
    111                 $pineconeApiKey = $this->pineconeHelper->getPineconeApiKey();
    112                 $pineconeHost = $this->pineconeHelper->getPineconeIndexHost();
    113                 $pineconeNamespace = $this->pineconeHelper->getPineconeNamespace();
    114                 $vectorSearchMinScore = $this->chatbotHelper->getVectorSearchMinScore();
    115 
    116                 if ($useCustomerApiKeysEnabled) {
    117                     $args = array_merge($args, [
    118                         'pinecone_api_key' => $pineconeApiKey,
    119                         'pinecone_index_host' =>  $pineconeHost,
    120                         'pinecone_namespace' => $pineconeNamespace,
    121                         'vector_search_min_score' => $vectorSearchMinScore
    122                     ]);
    123                 } else {
    124                     $args = array_merge($args, [
    125                         'pinecone_namespace' => $domainNamespace,
    126                         'vector_search_min_score' => $vectorSearchMinScore
    127                     ]);
    128                 }
    129             }
    130         }
    131 
    132         $this->setArgs($args);
    133 
    134         try {
    135             $success = true;
    136             if ($isChat) {
    137                 $reply = $this->apiHelper->sendChatRequest($message, $this->getArgs());
    138             } else {
    139                 $reply = $this->apiHelper->sendPromptRequest($message, $this->getArgs());
    140             }
    141         } catch (\Exception $e) {
    142             $reply = __('Sorry, a problem happened, please try again.', 'visualwebs-ml');
    143             $success = false;
    144         }
    145 
    146         wp_send_json([
    147             'result' => $reply,
    148             'success' => $success
    149         ]);
     70        if (empty($message)) {
     71            return array(
     72                'result' => '',
     73                'success' => false,
     74                'error' => __('Message is required.', 'visualwebs-ml'),
     75            );
     76        }
     77
     78        $payload = array(
     79            'message' => $message,
     80            'user_message' => $message,
     81            'platform' => 'wordpress-frontend',
     82            'store_id' => $this->dataHelper->getStoreId(),
     83            'params' => array(
     84                'session_id' => $this->chatbotHelper->getConversationUniqueSessionId(),
     85                'is_single_prompt' => !$isChat,
     86                'current_page_info' => $currentPageInfo,
     87                'instructions' => $this->chatbotHelper->getChatbotContextValue(),
     88                'offline_message' => $this->chatbotHelper->getChatbotOfflineMessage(),
     89            ),
     90        );
     91
     92        error_log('[Visualwebs ML][Chatbot][request] ' . wp_json_encode(array(
     93            'endpoint' => $this->chatbotHelper->getChatApiUrl(),
     94            'payload_keys' => array_keys($payload),
     95            'scope' => 'frontend',
     96        )));
     97
     98        $response = $this->apiHelper->sendWebhookRequest(
     99            $this->chatbotHelper->getChatApiUrl(),
     100            $payload,
     101            array('x-api-key' => $this->dataHelper->getApiKey())
     102        );
     103
     104        error_log('[Visualwebs ML][Chatbot][response] ' . wp_json_encode(array(
     105            'scope' => 'frontend',
     106            'success' => empty($response['error']),
     107            'has_result' => isset($response['result']) || isset($response['reply']) || isset($response['message']),
     108        )));
     109
     110        return array(
     111            'result' => isset($response['result'])
     112                ? $response['result']
     113                : (isset($response['reply']) ? $response['reply'] : (isset($response['message']) ? $response['message'] : '')),
     114            'success' => empty($response['error']),
     115            'error' => isset($response['error']) ? $response['error'] : '',
     116        );
     117    }
     118
     119    /**
     120     * Execute admin chatbot call via secure local proxy endpoint.
     121     *
     122     * @param array|null $input
     123     * @return array
     124     */
     125    public function execute_admin($input = null)
     126    {
     127        $input = is_array($input) ? $input : json_decode(file_get_contents('php://input'), true);
     128        if (!is_array($input)) {
     129            $input = array();
     130        }
     131
     132        $message = isset($input['message']) ? sanitize_text_field(wp_unslash($input['message'])) : '';
     133        if (empty($message)) {
     134            return array(
     135                'result' => '',
     136                'success' => false,
     137                'error' => __('Message is required.', 'visualwebs-ml'),
     138            );
     139        }
     140
     141        $payload = array(
     142            'message' => $message,
     143            'user_message' => $message,
     144            'platform' => 'wordpress-admin',
     145            'store_id' => $this->dataHelper->getStoreId(),
     146            'params' => array(
     147                'session_id' => 'admin_' . get_current_user_id() . '_' . wp_generate_uuid4(),
     148                'is_single_prompt' => false,
     149                'is_admin' => true,
     150                'current_page_info' => array(
     151                    'full_action_name' => DataHelper::getFullActionName(),
     152                    'params' => array(),
     153                ),
     154            ),
     155        );
     156
     157        error_log('[Visualwebs ML][Chatbot][request] ' . wp_json_encode(array(
     158            'endpoint' => $this->chatbotHelper->getChatApiUrl(),
     159            'payload_keys' => array_keys($payload),
     160            'scope' => 'admin',
     161            'user_id' => get_current_user_id(),
     162        )));
     163
     164        $response = $this->apiHelper->sendWebhookRequest(
     165            $this->chatbotHelper->getChatApiUrl(),
     166            $payload,
     167            array('x-api-key' => $this->dataHelper->getApiKey())
     168        );
     169
     170        error_log('[Visualwebs ML][Chatbot][response] ' . wp_json_encode(array(
     171            'scope' => 'admin',
     172            'success' => empty($response['error']),
     173            'has_result' => isset($response['result']) || isset($response['reply']) || isset($response['message']),
     174        )));
     175
     176        return array(
     177            'result' => isset($response['result'])
     178                ? $response['result']
     179                : (isset($response['reply']) ? $response['reply'] : (isset($response['message']) ? $response['message'] : '')),
     180            'success' => empty($response['error']),
     181            'error' => isset($response['error']) ? $response['error'] : '',
     182        );
    150183    }
    151184
  • visualwebs-ml/trunk/vendor/visualwebs-ml/Helper/Data.php

    r3339941 r3476789  
    3333
    3434    /**
     35     * Retrieve Visualwebs API key.
     36     *
     37     * @return string
     38     */
     39    public function getApiKey()
     40    {
     41        return (string) $this->getConfig('api_key', '');
     42    }
     43
     44    /**
     45     * Retrieve SaaS store UUID.
     46     *
     47     * @return string
     48     */
     49    public function getStoreId()
     50    {
     51        return (string) $this->getConfig('store_id', '');
     52    }
     53
     54    /**
    3555     * Check if the Visualwebs ML module is enabled.
    3656     *
     
    7898        return $isMlServicesEnabled && $isMainModuleEnabled && $mlServicesApiKey;
    7999    }
    80 
    81     /**
    82      * Check if the use of customer API keys is enabled.
    83      *
    84      * This method retrieves the configuration setting that determines whether
    85      * the use of customer OpenAI/PInecone API keys is enabled for the Visualwebs ML services.
    86      *
    87      * @return bool Returns true if the use of customer API keys is enabled, false otherwise.
    88      */
    89     public function getUseCustomerApiKeysEnabled()
    90     {
    91         $isUseCustomerApiKeysEnabled = $this->getConfig('use_customer_api_keys');
    92 
    93         return $isUseCustomerApiKeysEnabled;
    94     }
    95 
    96100    /**
    97101     * Check if Dynamic Pricing is enabled.
     
    123127
    124128    /**
    125      * Get the minimum profit percentage for Dynamic Pricing.
    126      *
    127      * @return float
    128      */
    129     public function getDynamicPricingMinProfit()
    130     {
    131         return (float) $this->getConfig('dynamic_pricing_min_profit', 10);
    132     }
    133 
    134     /**
    135      * Get the maximum profit percentage for Dynamic Pricing.
    136      *
    137      * @return float
    138      */
    139     public function getDynamicPricingMaxProfit()
    140     {
    141         return (float) $this->getConfig('dynamic_pricing_max_profit', 35);
    142     }
    143 
    144     /**
    145129     * Check if dynamic pricing can replace prices (enabled AND replace prices).
    146130     *
     
    150134    {
    151135        return $this->getIsDynamicPricingEnabled() && $this->getIsDynamicPricingReplacePrices();
    152     }
    153 
    154     /**
    155      * Check if storing dynamic data (like product price and stock) is enabled.
    156      *
    157      * @return bool
    158      */
    159     public function getIsStoreDynamicDataEnabled()
    160     {
    161         return (bool) $this->getConfig('vector_store_dynamic_data', false);
    162136    }
    163137
     
    197171        $currencySymbol = get_woocommerce_currency_symbol();
    198172        return $currencySymbol ?: "€";
    199     }
    200 
    201     /**
    202      * Retrieve user widgets configuration.
    203      *
    204      * This method fetches the user widgets configuration value from the store's scope configuration.
    205      *
    206      * @return string JSON encoded string of user widgets configuration.
    207      * Returns an empty JSON array "[]" if no configuration is found.
    208      */
    209 
    210     public function getUserWidgets()
    211     {
    212         $userWidgets = $this->getConfig('user_widgets');
    213         $processedWidgets = [];
    214 
    215         if (!empty($userWidgets['redux_repeater_data'])) {
    216             foreach ($userWidgets['redux_repeater_data'] as $index => $widgetData) {
    217                 $processedWidgets[] = [
    218                     'widget_title' => $userWidgets['widget_title'][$index] ?? '',
    219                     'widget_instructions' => $userWidgets['widget_instructions'][$index] ?? '',
    220                     'widget_render_el' => $userWidgets['widget_render_el'][$index] ?? '',
    221                     'widget_source_el' => $userWidgets['widget_source_el'][$index] ?? '',
    222                 ];
    223             }
    224         }
    225 
    226         return $processedWidgets;
    227173    }
    228174
     
    322268    {
    323269        $content = '';
    324         $isStoreDynamicDataEnabled = $this->getIsStoreDynamicDataEnabled();
     270        // Store dynamic data config was removed (now managed in SaaS feed generation)
     271        $isStoreDynamicDataEnabled = false;
    325272
    326273        if (post_type_exists($postType)) {
  • visualwebs-ml/trunk/vendor/visualwebs-ml/Helper/Enqueue.php

    r3337202 r3476789  
    33namespace Visualwebs\ML\Helper;
    44
    5 use Visualwebs\ML\Block\ChatWidget;
    6 use Visualwebs\ML\Block\Adminhtml\Widget;
    7 use Visualwebs\ML\Block\Adminhtml\Dashboard;
    8 use Visualwebs\ML\Helper\Data as Helper;
    9 use Visualwebs\ML\Model\Config\Source\MLModelType;
    10 
    115class Enqueue
    126{
    13     private $widget;
    14     private $dashboard;
    15     private $chatWidget;
    16 
    177    /**
    18      * Constructor to initialize dependencies.
     8     * Constructor.
    199     */
    2010    public function __construct()
    2111    {
    22         $this->widget = new Widget();
    23         $this->dashboard = new Dashboard();
    24         $this->chatWidget = new ChatWidget();
     12        // Lightweight enqueue class for 5.5.0+
    2513    }
    2614
     
    3220    public function enqueue_admin_scripts($hook)
    3321    {
    34         wp_enqueue_style('visualwebs-ml-admin-style', plugin_dir_url(__FILE__) . '../../../assets/css/admin-style.css', [], false);
    35 
    36         if ($this->widget->getIsWidgetEnabled()) {
    37 
    38             $nonce = $this->widget->getAppNonce();
    39             $cache_key = Helper::CACHE_TRANSIENT;
    40             $cached_data = get_transient($cache_key);
    41 
    42             if ($cached_data === false) {
    43                 $cached_data = [
    44                     'salesLiveDataset' => $this->widget->getStoreSalesData(),
    45                     'bestsellerLiveDataset' => $this->widget->getProductBestSellersData(),
    46                     'bestsellerMeta' => $this->widget->getProductBestSellersMetadata(),
    47                     'reviewsLiveDataset' => $this->widget->getProductsReviewsData(),
    48                     'customerRegistrationLiveDataset' => $this->widget->getCustomerRegistrationData(),
    49                     'MLApiUrl' => $this->widget->getMLApiUrl(),
    50                     'chatApiUrl' => $this->widget->getChatApiUrl(),
    51                     'timeseriesModel' => MLModelType::TYPE_TEMPORAL_SERIES_DEFAULT,
    52                     'sentimentModel' => MLModelType::TYPE_TEXT_SENTIMENT,
    53                     'spamModel' => MLModelType::TYPE_SPAM_CLASSIFIER,
    54                     'userWidgets' => $this->widget->getUserWidgets() ?: []
    55                 ];
    56 
    57                 set_transient($cache_key, $cached_data, HOUR_IN_SECONDS);
    58             }
    59 
    60             $dynamic_data = [
    61                 'productId' => $this->widget->getProduct() ? $this->widget->getProduct()->get_id() : '0',
    62                 'productRef' => $this->widget->getProduct() ? $this->widget->getProduct()->get_sku() : 'NA',
    63                 'productLiveDataset' => $this->widget->getProduct() ? $this->widget->getProductData() : [],
    64                 'userLanguage' => $this->widget->getCurrentAdminUserLanguageShort(),
    65                 'nonce' => $nonce
    66             ];
    67 
    68             $translated_titles = [
    69                 'salesPredictorTitle' => esc_html__('Sales Predictor', 'visualwebs-ml'),
    70                 'reviewsSentimentTitle' => esc_html__('Reviews Sentiment', 'visualwebs-ml'),
    71                 'bestsellersPredictorTitle' => esc_html__('Bestsellers Predictor', 'visualwebs-ml'),
    72                 'reviewsSpamTitle' => esc_html__('Reviews Spam', 'visualwebs-ml'),
    73                 'apiUsageTitle' => esc_html__('API usage', 'visualwebs-ml'),
    74                 'chatbotHistoryTitle' => esc_html__('Chatbot history', 'visualwebs-ml'),
    75                 'customerRegisterPredictorTitle' => esc_html__('Customer Register Predictor', 'visualwebs-ml'),
    76                 'productPredictorTitle' => esc_html__('Product Predictor', 'visualwebs-ml'),
    77                 'promptTitle' => esc_html__('Prompt', 'visualwebs-ml'),
    78                 'seoGeneratorTitle' => esc_html__('SEO generator', 'visualwebs-ml'),
    79                 'seoGeneratorDescriptionTitle' => esc_html__('SEO generator - Description', 'visualwebs-ml'),
    80                 'seoGeneratorShortDescriptionTitle' => esc_html__('SEO generator - Short description', 'visualwebs-ml')
    81             ];
    82 
    83             $final_data = array_merge($cached_data, $dynamic_data, $translated_titles);
    84 
    85             wp_enqueue_script(
    86                 'visualwebs-ml-app-remote-js',
    87                 'https://ml.visualwebs.eu/app.js',
    88                 array(),
    89                 false,
    90                 ['in_footer' => true, 'strategy' => 'defer']
    91             );
    92 
    93             wp_enqueue_script(
    94                 'visualwebs-widget-js',
    95                 plugin_dir_url(__FILE__) . '../../../assets/js/widget.js',
    96                 array(),
    97                 false,
    98                 ['in_footer' => true, 'strategy' => 'defer']
    99             );
    100 
    101             wp_localize_script('visualwebs-widget-js', 'visualwebsAiWidgetData', $final_data);
    102 
    103         }
    104 
    105         // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required for this specific use case.
    106         if (isset($_GET['page']) && $_GET['page'] === 'visualwebs-ml-dashboard') {
    107 
    108             if ($this->dashboard->getIsVisualwebsMlServicesEnabled()) {
    109 
    110                 $stats = $this->dashboard->getStats();
    111                 $userLanguage = $this->dashboard->getCurrentAdminUserLanguageShort();
    112                 $currency_symbol =  html_entity_decode($this->dashboard->getStoreCurrencySign());
    113                 $nonce = $this->dashboard->getAppNonce();
    114                 $clear_cache_url = esc_url(admin_url('admin.php?page=visualwebs-ml-dashboard&clear_cache=true&_wpnonce=' . wp_create_nonce('clear_cache_action')));
    115 
    116                 wp_enqueue_script(
    117                     'visualwebs-dashboard-js',
    118                     plugin_dir_url(__FILE__) . '../../../assets/js/dashboard.js',
    119                     array(),
    120                     false,
    121                     ['in_footer' => true, 'strategy' => 'defer']
    122                 );
    123 
    124                 wp_localize_script('visualwebs-dashboard-js', 'visualwebsAiDashboardData', [
    125                     'stats' => $stats,
    126                     'currencySymbol' => $currency_symbol,
    127                     'clearCacheUrl' => $clear_cache_url,
    128                     'userLanguage' => $userLanguage,
    129                     'nonce' => $nonce
    130                 ]);
    131 
    132             }
    133         }
    134 
    135         // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required for this specific use case.
    136         if (isset($_GET['page']) && $_GET['page'] === 'visualwebs-ml-semantic-add') {
    137             wp_enqueue_script(
    138                 'visualwebs-ml-semantic-search',
    139                 plugin_dir_url(__FILE__) . '../../../assets/js/semantic-search.js',
    140                 array('jquery'),
    141                 false,
    142                 ['in_footer' => true, 'strategy' => 'defer']
    143             );
    144 
    145             wp_localize_script(
    146                 'visualwebs-ml-semantic-search',
    147                 'visualwebs_ml_search',
    148                 array(
    149                     'ajax_url' => admin_url('admin-ajax.php')
    150                 )
    151             );
    152 
    153         }
    154 
     22        // Legacy widgets and dashboard removed in 5.5.0 (now managed in SaaS dashboard)
     23        // Legacy chatbot removed in 5.5.0 (now uses n8n-chatbot-widget.php template)
     24        // Legacy semantic search admin removed in 5.5.0
     25        // Legacy admin styles removed in 5.5.0
    15526    }
    15627
     
    16031    public function enqueue_frontend_scripts()
    16132    {
    162         if ($this->chatWidget->getIsChatbotEnabled()) {
    163             $chatApiUrl = $this->chatWidget->getChatApiUrl();
    164             $chatbotCurrentPageInfo = $this->chatWidget->getChatbotCurrentPageInfoForWidget();
    165             $linksColor =  $this->chatWidget->getChatbotMainBackgroundColor();
    166             $mainBackgroundColor = $this->chatWidget->getChatbotMainBackgroundColor();
    167             $chatbotHeaderTitle = $this->chatWidget->getChatbotHeaderTitle();
    168             $chatbotDisclaimerUrl = $this->chatWidget->getChatbotDisclaimerUrl();
    169             $chatbotOfflineMessage = $this->chatWidget->getChatbotOfflineMessage();
    170             $chatbotLogoUrl = $this->chatWidget->getChatbotLogoUrl();
    171             $userLanguage = $this->chatWidget->getCurrentFrontendLanguageShort();
    172 
    173             wp_enqueue_script(
    174                 'visualwebs-ml-app-remote-js',
    175                 'https://ml.visualwebs.eu/app.js',
    176                 array(),
    177                 false,
    178                 ['in_footer' => true, 'strategy' => 'defer']
    179             );
    180 
    181             wp_enqueue_script('visualwebs-chatbot-js', plugin_dir_url(__FILE__) . '../../../assets/js/chatbot.js', ['visualwebs-ml-app-remote-js'], false, ['in_footer' => true, 'strategy' => 'defer']);
    182 
    183             $inline_data = 'var visualwebsAiChatbotData = ' . wp_json_encode([
    184               "chatApiUrl" => $chatApiUrl,
    185               "uniqueSessionId" => '',
    186               "chatbotCurrentPageInfo" => $chatbotCurrentPageInfo,
    187               "mainBackgroundColor" => $mainBackgroundColor,
    188               "linksColor" => $linksColor,
    189               "chatbotHeaderTitle" => $chatbotHeaderTitle,
    190               "chatbotDisclaimerUrl" => $chatbotDisclaimerUrl,
    191               "chatbotOfflineMessage" => $chatbotOfflineMessage,
    192               "chatbotLogoUrl" => $chatbotLogoUrl,
    193               "userLanguage" => $userLanguage,
    194               'restUrl' => esc_url_raw(rest_url('visualwebs-ml/v1/get-nonce'))
    195             ]) . ';';
    196 
    197             wp_add_inline_script('visualwebs-chatbot-js', $inline_data, 'before');
    198         }
     33        // Legacy chatbot enqueue removed in 5.5.0
     34        // Now uses n8n-chatbot-widget.php template loaded via wp_footer hook
     35        // See: class-visualwebs-ml.php render_frontend_chatbot_widget()
    19936    }
    20037}
  • visualwebs-ml/trunk/vendor/visualwebs-ml/Helper/Visualwebs.php

    r3337157 r3476789  
    111111
    112112    /**
     113     * Sends payload to an explicit webhook URL.
     114     *
     115     * @param string $url
     116     * @param array  $data
     117     * @param array  $headers
     118     * @return array
     119     */
     120    public function sendWebhookRequest($url, $data = [], $headers = [])
     121    {
     122        $visualwebsApi = $this->getVisualwebsInstance();
     123        return $visualwebsApi->sendWebhookRequest($url, $data, $headers);
     124    }
     125
     126    /**
    113127     * Sends a vector upsert request to the Visualwebs API.
    114128     *
  • visualwebs-ml/trunk/vendor/visualwebs-ml/Model/Api/Visualwebs.php

    r3320111 r3476789  
    9797
    9898    /**
     99     * Sends a request to an explicit webhook URL (used by SaaS chatbot n8n endpoint).
     100     *
     101     * @param string $url
     102     * @param array  $data
     103     * @param array  $extraHeaders
     104     * @return array
     105     */
     106    public function sendWebhookRequest($url, $data = [], $extraHeaders = [])
     107    {
     108        if (empty($url)) {
     109            return [
     110                'error' => true,
     111                'message' => 'Missing webhook URL',
     112            ];
     113        }
     114
     115        $headers = [
     116            'Content-Type' => 'application/json',
     117            'Accept' => 'application/json',
     118        ];
     119
     120        // Keep compatibility with existing API key auth model.
     121        if (!empty($this->apiKey)) {
     122            $headers['Authorization'] = 'Bearer ' . $this->apiKey;
     123            $headers['x-api-key'] = $this->apiKey;
     124        }
     125
     126        if (!empty($extraHeaders) && is_array($extraHeaders)) {
     127            $headers = array_merge($headers, $extraHeaders);
     128        }
     129
     130        $response = wp_remote_post(
     131            $url,
     132            [
     133                'headers' => $headers,
     134                'timeout' => 20,
     135                'body' => wp_json_encode($data),
     136            ]
     137        );
     138
     139        if (is_wp_error($response)) {
     140            return [
     141                'error' => true,
     142                'message' => $response->get_error_message(),
     143            ];
     144        }
     145
     146        $status = (int) wp_remote_retrieve_response_code($response);
     147        $body = wp_remote_retrieve_body($response);
     148        $decoded = json_decode($body, true);
     149
     150        if ($status < 200 || $status >= 300) {
     151            return [
     152                'error' => true,
     153                'message' => 'Webhook response status: ' . $status,
     154                'raw' => is_array($decoded) ? $decoded : $body,
     155            ];
     156        }
     157
     158        return is_array($decoded) ? $decoded : ['result' => (string) $body];
     159    }
     160
     161    /**
    99162     * Sends a spam prediction request to the specified endpoint.
    100163     *
  • visualwebs-ml/trunk/vendor/visualwebs-ml/Model/MLModel.php

    r3326860 r3476789  
    658658
    659659        $tomorrow = gmdate('Y-m-d', strtotime('+1 day'));
    660         $min_pct = $this->mainHelper->getDynamicPricingMinProfit();
    661         $max_pct = $this->mainHelper->getDynamicPricingMaxProfit();
     660        // Default profit margins (config removed, now managed in SaaS)
     661        $min_pct = 10;
     662        $max_pct = 35;
    662663        $result = [];
    663664
  • visualwebs-ml/trunk/visualwebs-ml.php

    r3337202 r3476789  
    44Requires Plugins: redux-framework, woocommerce
    55Description: Plugin to embed chatGPT, chatbot, and Machine Learning Widgets in WP.
    6 Version: 5.4.3
     6Version: 5.5.0
    77Requires PHP: 7.4
    88Author: Visualwebs
    99Author URI: https://visualwebs.eu/product/ai-cloud-suite/
    10 Tested up to: 6.8
     10Tested up to: 6.9
    1111License: GPLv2 or later
    1212License URI: https://visualwebs.eu/terms-and-conditions/
Note: See TracChangeset for help on using the changeset viewer.