settings = get_option( 'aica_settings', array() ); } /** * Generate AI response * * @param string $user_message User message * @param string $session_id Session ID * @param array $context Additional context * @return array Response with message, products, actions */ public function generate_response( $user_message, $session_id, $context = array() ) { try { // Get conversation history $history = $this->get_conversation_history( $session_id ); // Retrieve relevant products using RAG $relevant_products = $this->retrieve_relevant_products( $user_message ); // Build system prompt $system_prompt = $this->build_system_prompt( $relevant_products, $context ); // Build messages array $messages = array_merge( array( array( 'role' => 'system', 'content' => $system_prompt, ), ), $history, array( array( 'role' => 'user', 'content' => $user_message, ), ) ); // Call AI provider $provider = $this->settings['ai_provider'] ?? 'openai'; switch ( $provider ) { case 'openai': $response = $this->call_openai( $messages ); break; case 'anthropic': $response = $this->call_anthropic( $messages ); break; case 'google': $response = $this->call_google( $messages ); break; case 'openrouter': $response = $this->call_openrouter( $messages ); break; default: throw new Exception( __( 'Invalid AI provider selected. Please check your settings.', 'aica-smart-ai-commerce-assistant' ) ); } // Parse response for structured data (products, actions) return $this->parse_ai_response( $response, $relevant_products ); } catch ( Exception $e ) { // Log the actual error for debugging error_log( 'AICA Error: ' . $e->getMessage() ); // Return user-friendly error message $user_message = $this->get_user_friendly_error( $e->getMessage() ); // Return error as a response instead of throwing return array( 'message' => $user_message, 'products' => array(), 'actions' => array(), 'error' => true, ); } } /** * Convert technical error to user-friendly message * * @param string $error_message Technical error message * @return string User-friendly error message */ private function get_user_friendly_error( $error_message ) { $error_lower = strtolower( $error_message ); // API key errors if ( strpos( $error_lower, 'api key' ) !== false || strpos( $error_lower, 'authentication' ) !== false || strpos( $error_lower, 'unauthorized' ) !== false ) { return __( 'The AI service is not properly configured. Please contact the site administrator to set up the API key.', 'aica-smart-ai-commerce-assistant' ); } // Rate limit errors if ( strpos( $error_lower, 'rate limit' ) !== false || strpos( $error_lower, 'quota' ) !== false || strpos( $error_lower, 'too many requests' ) !== false ) { return __( 'The AI service is temporarily busy. Please try again in a few minutes.', 'aica-smart-ai-commerce-assistant' ); } // Network/timeout errors if ( strpos( $error_lower, 'timeout' ) !== false || strpos( $error_lower, 'connection' ) !== false || strpos( $error_lower, 'network' ) !== false ) { return __( 'Unable to connect to the AI service. Please check your internet connection and try again.', 'aica-smart-ai-commerce-assistant' ); } // Invalid model errors if ( strpos( $error_lower, 'model' ) !== false && strpos( $error_lower, 'not found' ) !== false ) { return __( 'The selected AI model is not available. Please contact the site administrator.', 'aica-smart-ai-commerce-assistant' ); } // Insufficient credits/billing errors if ( strpos( $error_lower, 'insufficient' ) !== false || strpos( $error_lower, 'billing' ) !== false || strpos( $error_lower, 'payment' ) !== false ) { return __( 'The AI service account needs attention. Please contact the site administrator.', 'aica-smart-ai-commerce-assistant' ); } // Content policy violations if ( strpos( $error_lower, 'content policy' ) !== false || strpos( $error_lower, 'safety' ) !== false ) { return __( 'Your message could not be processed. Please rephrase your question and try again.', 'aica-smart-ai-commerce-assistant' ); } // Generic fallback return __( 'Sorry, I encountered an error while processing your request. Please try again or contact support if the problem persists.', 'aica-smart-ai-commerce-assistant' ); } /** * Retrieve relevant products using RAG * * @param string $query User query * @return array Products array */ private function retrieve_relevant_products( $query ) { global $wpdb; $table = $wpdb->prefix . 'aica_indexed_products'; $max_products = intval( $this->settings['max_products_per_response'] ?? 5 ); // Check if embeddings are enabled if ( isset( $this->settings['enable_embeddings'] ) && $this->settings['enable_embeddings'] === 'yes' ) { // Use embedding-based search (we'll implement this) return $this->embedding_based_search( $query, $max_products ); } // Fallback to full-text search $cache_key = 'aica_relevant_products_' . md5( $query ); $products = wp_cache_get( $cache_key, 'aica_ai_engine' ); if ( false !== $products ) { return $products; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $results = $wpdb->get_results( $wpdb->prepare( "SELECT product_id, content, price, stock_quantity, metadata FROM {$wpdb->prefix}aica_indexed_products WHERE MATCH(content) AGAINST(%s IN NATURAL LANGUAGE MODE) AND stock_quantity > 0 LIMIT %d", $query, $max_products ) ); $products = array(); foreach ( $results as $row ) { $product = wc_get_product( $row->product_id ); if ( $product ) { $products[] = array( 'id' => $product->get_id(), 'name' => $product->get_name(), 'price' => $product->get_price(), 'regular_price' => $product->get_regular_price(), 'sale_price' => $product->get_sale_price(), 'description' => wp_trim_words( $product->get_description(), 50 ), 'short_description' => wp_trim_words( $product->get_short_description(), 30 ), 'image' => wp_get_attachment_url( $product->get_image_id() ), 'url' => $product->get_permalink(), 'in_stock' => $product->is_in_stock(), 'stock_quantity' => $product->get_stock_quantity(), 'categories' => wp_get_post_terms( $product->get_id(), 'product_cat', array( 'fields' => 'names' ) ), ); } } wp_cache_set( $cache_key, $products, 'aica_ai_engine', HOUR_IN_SECONDS ); return $products; } /** * Embedding-based search (advanced RAG) * * @param string $query Search query * @param int $limit Max results * @return array Products */ private function embedding_based_search( $query, $limit ) { // Generate embedding for query $query_embedding = $this->generate_embedding( $query ); if ( !$query_embedding ) { // Fallback to regular search return $this->retrieve_relevant_products( $query ); } global $wpdb; $table = $wpdb->prefix . 'aica_indexed_products'; // Get all products with embeddings with caching $rows = wp_cache_get( 'aica_all_indexed_embeddings', 'aica_ai_engine' ); if ( false === $rows ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $rows = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}aica_indexed_products WHERE embedding IS NOT NULL AND stock_quantity > 0" ); wp_cache_set( 'aica_all_indexed_embeddings', $rows, 'aica_ai_engine', HOUR_IN_SECONDS ); } // Calculate cosine similarity $similarities = array(); foreach ( $rows as $row ) { $product_embedding = json_decode( $row->embedding, true ); if ( $product_embedding ) { $similarity = $this->cosine_similarity( $query_embedding, $product_embedding ); $similarities[] = array( 'product_id' => $row->product_id, 'similarity' => $similarity, ); } } // Sort by similarity usort( $similarities, function ( $a, $b ) { return $b['similarity'] <=> $a['similarity']; } ); // Get top products $top_products = array_slice( $similarities, 0, $limit ); $products = array(); foreach ( $top_products as $item ) { $product = wc_get_product( $item['product_id'] ); if ( $product ) { $products[] = array( 'id' => $product->get_id(), 'name' => $product->get_name(), 'price' => $product->get_price(), 'regular_price' => $product->get_regular_price(), 'sale_price' => $product->get_sale_price(), 'description' => wp_trim_words( $product->get_description(), 50 ), 'short_description' => wp_trim_words( $product->get_short_description(), 30 ), 'image' => wp_get_attachment_url( $product->get_image_id() ), 'url' => $product->get_permalink(), 'in_stock' => $product->is_in_stock(), 'stock_quantity' => $product->get_stock_quantity(), 'categories' => wp_get_post_terms( $product->get_id(), 'product_cat', array( 'fields' => 'names' ) ), 'relevance_score' => round( $item['similarity'], 2 ), ); } } return $products; } /** * Generate embedding using OpenAI * * @param string $text Text to embed * @return array|false Embedding vector or false on failure */ public function generate_embedding( $text ) { $api_key = $this->settings['openai_api_key'] ?? ''; if ( empty( $api_key ) ) { return false; } $model = $this->settings['embedding_model'] ?? 'text-embedding-3-small'; $response = wp_remote_post( 'https://api.openai.com/v1/embeddings', array( 'headers' => array( 'Authorization' => 'Bearer ' . $api_key, 'Content-Type' => 'application/json', ), 'body' => wp_json_encode( array( 'model' => $model, 'input' => $text, ) ), 'timeout' => 30, ) ); if ( is_wp_error( $response ) ) { return false; } $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( isset( $body['data'][0]['embedding'] ) ) { return $body['data'][0]['embedding']; } return false; } /** * Calculate cosine similarity * * @param array $vec1 Vector 1 * @param array $vec2 Vector 2 * @return float Similarity score */ private function cosine_similarity( $vec1, $vec2 ) { $dot_product = 0; $magnitude1 = 0; $magnitude2 = 0; for ( $i = 0; $i < count( $vec1 ); $i++ ) { $dot_product += $vec1[ $i ] * $vec2[ $i ]; $magnitude1 += $vec1[ $i ] * $vec1[ $i ]; $magnitude2 += $vec2[ $i ] * $vec2[ $i ]; } $magnitude1 = sqrt( $magnitude1 ); $magnitude2 = sqrt( $magnitude2 ); if ( $magnitude1 == 0 || $magnitude2 == 0 ) { return 0; } return $dot_product / ( $magnitude1 * $magnitude2 ); } /** * Build system prompt * * @param array $products Products context * @param array $context Additional context * @return string System prompt */ private function build_system_prompt( $products, $context ) { $store_name = get_bloginfo( 'name' ); $prompt = "You are a helpful AI shopping assistant for {$store_name}. "; $prompt .= 'Your role is to help customers find products, answer questions, and assist with purchases. '; $currency_symbol = html_entity_decode( get_woocommerce_currency_symbol(), ENT_QUOTES, 'UTF-8' ); $price_example = html_entity_decode( strip_tags( wc_price( 1234.56 ) ), ENT_QUOTES, 'UTF-8' ); $prompt .= "Be friendly, concise, and helpful.\n"; $prompt .= "Prices are in " . get_woocommerce_currency() . " ({$currency_symbol}). Always format prices exactly like this: {$price_example}\n\n"; if ( !empty( $products ) ) { $prompt .= "Here are some relevant products from our catalog:\n\n"; foreach ( $products as $product ) { $prompt .= "- {$product['name']} (ID: {$product['id']})\n"; $prompt .= ' Price: ' . html_entity_decode( strip_tags( wc_price( $product['price'] ) ), ENT_QUOTES, 'UTF-8' ) . "\n"; if ( !empty( $product['short_description'] ) ) { $prompt .= " Description: {$product['short_description']}\n"; } $prompt .= ' Stock: ' . ( $product['in_stock'] ? 'In stock' : 'Out of stock' ) . "\n\n"; } } $prompt .= "\nWhen recommending products, mention their ID so the system can display them properly."; $prompt .= "\nIf asked to add a product to cart, respond with: ADD_TO_CART:product_id"; return $prompt; } /** * Get conversation history * * @param string $session_id Session ID * @param int $limit Max messages * @return array Messages array */ private function get_conversation_history( $session_id, $limit = 10 ) { global $wpdb; $table = $wpdb->prefix . 'aica_conversations'; $cache_key = 'aica_conversation_history_' . $session_id; $results = wp_cache_get( $cache_key, 'aica_ai_engine' ); if ( false === $results ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $results = $wpdb->get_results( $wpdb->prepare( "SELECT message, role FROM {$wpdb->prefix}aica_conversations WHERE session_id = %s ORDER BY created_at DESC LIMIT %d", $session_id, $limit ) ); wp_cache_set( $cache_key, $results, 'aica_ai_engine', 300 ); // Cache for 5 minutes } $messages = array(); foreach ( array_reverse( $results ) as $row ) { $messages[] = array( 'role' => $row->role === 'user' ? 'user' : 'assistant', 'content' => $row->message, ); } return $messages; } /** * Call OpenAI API * * @param array $messages Messages array * @return string Response text * @throws Exception */ private function call_openai( $messages ) { $api_key = $this->settings['openai_api_key'] ?? ''; if ( empty( $api_key ) ) { throw new Exception( esc_html__( 'OpenAI API key not configured', 'aica-smart-ai-commerce-assistant' ) ); } $model = $this->settings['openai_model'] ?? 'gpt-4o-mini'; $temperature = floatval( $this->settings['temperature'] ?? 0.7 ); $max_tokens = intval( $this->settings['max_tokens'] ?? 500 ); $response = wp_remote_post( 'https://api.openai.com/v1/chat/completions', array( 'headers' => array( 'Authorization' => 'Bearer ' . $api_key, 'Content-Type' => 'application/json', ), 'body' => wp_json_encode( array( 'model' => $model, 'messages' => $messages, 'temperature' => $temperature, 'max_tokens' => $max_tokens, ) ), 'timeout' => 30, ) ); if ( is_wp_error( $response ) ) { throw new Exception( esc_html( $response->get_error_message() ) ); } $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( isset( $body['error'] ) ) { throw new Exception( esc_html( $body['error']['message'] ) ); } return $body['choices'][0]['message']['content'] ?? ''; } /** * Call Anthropic Claude API * * @param array $messages Messages array * @return string Response text * @throws Exception */ private function call_anthropic( $messages ) { $api_key = $this->settings['anthropic_api_key'] ?? ''; if ( empty( $api_key ) ) { throw new Exception( esc_html__( 'Anthropic API key not configured', 'aica-smart-ai-commerce-assistant' ) ); } $model = $this->settings['anthropic_model'] ?? 'claude-3-5-sonnet-20241022'; $temperature = floatval( $this->settings['temperature'] ?? 0.7 ); $max_tokens = intval( $this->settings['max_tokens'] ?? 500 ); // Extract system message $system = ''; $conversation = array(); foreach ( $messages as $msg ) { if ( $msg['role'] === 'system' ) { $system = $msg['content']; } else { $conversation[] = $msg; } } $response = wp_remote_post( 'https://api.anthropic.com/v1/messages', array( 'headers' => array( 'x-api-key' => $api_key, 'anthropic-version' => '2023-06-01', 'Content-Type' => 'application/json', ), 'body' => wp_json_encode( array( 'model' => $model, 'max_tokens' => $max_tokens, 'temperature' => $temperature, 'system' => $system, 'messages' => $conversation, ) ), 'timeout' => 30, ) ); if ( is_wp_error( $response ) ) { throw new Exception( esc_html( $response->get_error_message() ) ); } $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( isset( $body['error'] ) ) { throw new Exception( esc_html( $body['error']['message'] ) ); } return $body['content'][0]['text'] ?? ''; } /** * Call Google Gemini API * * @param array $messages Messages array * @return string Response text * @throws Exception */ private function call_google( $messages ) { $api_key = $this->settings['google_api_key'] ?? ''; if ( empty( $api_key ) ) { throw new Exception( esc_html__( 'Google API key not configured', 'aica-smart-ai-commerce-assistant' ) ); } $model = $this->settings['google_model'] ?? 'gemini-2.5-flash'; $temperature = floatval( $this->settings['temperature'] ?? 0.7 ); $debug_mode = defined( 'WP_DEBUG' ) && WP_DEBUG; // Convert messages to Gemini format $contents = array(); $system_instruction = ''; foreach ( $messages as $msg ) { if ( $msg['role'] === 'system' ) { $system_instruction = $msg['content']; } else { $contents[] = array( 'role' => $msg['role'] === 'user' ? 'user' : 'model', 'parts' => array( array( 'text' => $msg['content'] ) ), ); } } // If we have system instruction, prepend it to first user message if ( !empty( $system_instruction ) && !empty( $contents ) ) { foreach ( $contents as $key => $content ) { if ( $content['role'] === 'user' ) { $contents[ $key ]['parts'][0]['text'] = $system_instruction . "\n\n" . $contents[ $key ]['parts'][0]['text']; break; } } } // Build request body matching your Angular code structure $body = array( 'contents' => $contents, 'generationConfig' => array( 'temperature' => $temperature, 'maxOutputTokens' => intval( $this->settings['max_tokens'] ?? 500 ), ), ); // Use correct URL format (no 'models/' prefix in URL path) $url = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent"; if ( $debug_mode ) { error_log( 'AICA: Calling Gemini API with model: ' . $model ); } $response = wp_remote_post( $url, array( 'headers' => array( 'Content-Type' => 'application/json', 'x-goog-api-key' => $api_key, ), 'body' => wp_json_encode( $body ), 'timeout' => 30, ) ); if ( is_wp_error( $response ) ) { if ( $debug_mode ) { error_log( 'AICA: Gemini WP_Error: ' . $response->get_error_message() ); } throw new Exception( esc_html( $response->get_error_message() ) ); } $response_code = wp_remote_retrieve_response_code( $response ); $body_response = json_decode( wp_remote_retrieve_body( $response ), true ); if ( $response_code !== 200 ) { if ( $debug_mode ) { error_log( 'AICA: Gemini API HTTP ' . $response_code ); error_log( 'AICA: Response: ' . print_r( $body_response, true ) ); } } if ( isset( $body_response['error'] ) ) { $error_message = $body_response['error']['message'] ?? 'Unknown error'; throw new Exception( esc_html( $error_message ) ); } if ( !isset( $body_response['candidates'][0]['content']['parts'][0]['text'] ) ) { if ( $debug_mode ) { error_log( 'AICA: Unexpected response: ' . print_r( $body_response, true ) ); } throw new Exception( 'Unexpected response format from Gemini API' ); } $response_text = $body_response['candidates'][0]['content']['parts'][0]['text']; return $response_text; } /** * Call OpenRouter API * * @param array $messages Messages array * @return string Response text * @throws Exception */ private function call_openrouter( $messages ) { $api_key = $this->settings['openrouter_api_key'] ?? ''; if ( empty( $api_key ) ) { throw new Exception( esc_html__( 'OpenRouter API key not configured', 'aica-smart-ai-commerce-assistant' ) ); } $model = $this->settings['openrouter_model'] ?? 'anthropic/claude-3.5-sonnet'; $temperature = floatval( $this->settings['temperature'] ?? 0.7 ); $max_tokens = intval( $this->settings['max_tokens'] ?? 500 ); $response = wp_remote_post( 'https://openrouter.ai/api/v1/chat/completions', array( 'headers' => array( 'Authorization' => 'Bearer ' . $api_key, 'Content-Type' => 'application/json', 'HTTP-Referer' => home_url(), ), 'body' => wp_json_encode( array( 'model' => $model, 'messages' => $messages, 'temperature' => $temperature, 'max_tokens' => $max_tokens, ) ), 'timeout' => 30, ) ); if ( is_wp_error( $response ) ) { throw new Exception( esc_html( $response->get_error_message() ) ); } $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( isset( $body['error'] ) ) { throw new Exception( esc_html( $body['error']['message'] ) ); } return $body['choices'][0]['message']['content'] ?? ''; } /** * Parse AI response for structured data * * @param string $response AI response text * @param array $available_products Available products * @return array Parsed response */ private function parse_ai_response( $response, $available_products ) { $result = array( 'message' => $response, 'products' => array(), 'actions' => array(), ); // Check for ADD_TO_CART command if ( preg_match( '/ADD_TO_CART:(\d+)/', $response, $matches ) ) { $result['actions'][] = array( 'type' => 'add_to_cart', 'product_id' => intval( $matches[1] ), ); // Remove command from message $result['message'] = trim( str_replace( $matches[0], '', $response ) ); } // Check if products are mentioned foreach ( $available_products as $product ) { if ( stripos( $response, (string) $product['id'] ) !== false || stripos( $response, $product['name'] ) !== false ) { $result['products'][] = $product; } } return $result; } }