PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.2.4
ShopBuilder – WooCommerce Builder For Elementor v3.2.4
3.4.2 3.4.1 3.4.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 All 63 releases
shopbuilder / app / AI / Embedding / DataEmbedding.php

DataEmbedding.php in ShopBuilder – WooCommerce Builder For Elementor 3.2.4, at app/AI/Embedding/DataEmbedding.php

153 lines 4.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class DataEmbedding
4 *
5 * Handles the generation, storage, and retrieval of AI-powered text embeddings
6 * for WooCommerce products using supported AI services like OpenAI, Gemini,
7 * or DeepSeek. Also provides semantic search capabilities using cosine similarity.
8 *
9 * @package RadiusTheme\SB\AI\Embedding
10 * @since 1.0.0
11 */
12
13 namespace RadiusTheme\SB\AI\Embedding;
14
15 use RadiusTheme\SB\AI\DB\AIDB;
16 use RadiusTheme\SB\AI\AIFns;
17 use RadiusTheme\SB\Traits\SingletonTrait;
18
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit( 'This script cannot be accessed directly.' );
21 }
22
23 /**
24 * Class DataEmbedding
25 *
26 * @package RadiusTheme\SB\AI\Embedding
27 * @since 1.0.0
28 */
29 class DataEmbedding {
30 /**
31 * Singleton Trait.
32 */
33 use SingletonTrait;
34
35 /**
36 * Generate and store an AI embedding for a product.
37 *
38 * @param int $product_id The product ID.
39 * @param string $title The product title.
40 * @param string $content The product content or description.
41 *
42 * @return bool True on success, false on failure.
43 */
44 public function generate_and_store( $product_id, $title, $content ) {
45 $ai_data = AIFns::activated_ai_data();
46 if ( empty( $ai_data['api_key'] ) ) {
47 return false;
48 }
49
50 $text = $title . ' ' . wp_strip_all_tags( $content );
51 $ai_service = AIFns::initializeAIService();
52 $embedding = $ai_service->generateEmbedding( $text );
53 if ( empty( $embedding ) || ! is_array( $embedding ) ) {
54 return false;
55 }
56 $info = [
57 'word_count' => str_word_count( $text ),
58 'source' => 'product',
59 ];
60 $result = AIDB::upsert_embeding( $product_id, $title, $embedding, $info );
61 return ! empty( $result );
62 }
63
64 /**
65 * Perform a semantic search based on a given query.
66 *
67 * @param string $query The user search query.
68 * @param int $limit Optional. Number of results to return. Default 0 (all).
69 *
70 * @return array List of matching product titles.
71 */
72 public function search( $query, $limit = 5 ) {
73 $ai_service = AIFns::initializeAIService();
74 $query_embedding = $ai_service->generateEmbedding( $query );
75 if ( empty( $query_embedding ) || ! is_array( $query_embedding ) ) {
76 return [];
77 }
78 // Log query embedding.
79 $rows = AIDB::get_all();
80 if ( empty( $rows ) ) {
81 return [];
82 }
83 $results = $this->find_similar( $query_embedding, $rows, $limit );
84 return wp_list_pluck( $results, 'post_id' );
85 }
86
87 /**
88 * Find the most semantically similar embeddings using cosine similarity.
89 *
90 * Optimized to reduce redundant computations and use a dedicated
91 * score calculation method for better maintainability.
92 *
93 * @param array $embedding The query embedding vector to compare.
94 * @param array $rows The stored embedding records from the database.
95 * @param int $limit Optional. Number of top matches to return. Default 5.
96 *
97 * @return array List of matched items with product ID, title, and similarity score.
98 */
99 public function find_similar( array $embedding, array $rows, int $limit = 5 ): array {
100 $minimum_match = AIFns::get_embedding_minimum_accuracy();
101 $matches = [];
102 foreach ( $rows as $row ) {
103 if ( empty( $row['embedding'] ) ) {
104 continue;
105 }
106 $vector = maybe_unserialize( $row['embedding'] );
107 if ( ! is_array( $vector ) ) {
108 continue;
109 }
110 // Avoid redundant norm computation if similarity calc includes it.
111 $score = $this->calculate_similarity_score( $embedding, $vector );
112 if ( $score >= $minimum_match ) {
113 $matches[] = [
114 'post_id' => isset( $row['product_id'] ) ? (int) $row['product_id'] : 0,
115 'post_title' => isset( $row['title'] ) ? sanitize_text_field( $row['title'] ) : '',
116 'score' => round( $score, 4 ),
117 ];
118 }
119 }
120 if ( empty( $matches ) ) {
121 return [];
122 }
123 // Use array_multisort for faster sorting on large datasets.
124 array_multisort( array_column( $matches, 'score' ), SORT_DESC, $matches );
125 return $limit > 0 ? array_slice( $matches, 0, $limit ) : $matches;
126 }
127
128
129 /**
130 * Calculate cosine similarity score between query and stored embedding.
131 *
132 * This version avoids redundant normalization by reusing the precomputed
133 * query norm and computes the dot product and target norm in one pass.
134 *
135 * @param array $query_vec The query embedding vector.
136 * @param array $target_vec The stored embedding vector.
137 *
138 * @return float Cosine similarity score (0.0–1.0).
139 */
140 protected function calculate_similarity_score( array $query_vec, array $target_vec ): float {
141 $dot = 0.0;
142 $normA = 0.0;
143 $normB = 0.0;
144 $count = min( count( $query_vec ), count( $target_vec ) );
145 for ( $i = 0; $i < $count; $i++ ) {
146 $dot += $query_vec[ $i ] * $target_vec[ $i ];
147 $normA += $query_vec[ $i ] ** 2;
148 $normB += $target_vec[ $i ] ** 2;
149 }
150 return $dot / ( sqrt( $normA ) * sqrt( $normB ) );
151 }
152 }
153