PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.2.2
ShopBuilder – WooCommerce Builder For Elementor v3.2.2
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.2, at app/AI/Embedding/DataEmbedding.php

157 lines 4.9 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
81 if ( empty( $rows ) ) {
82 return [];
83 }
84 $results = $this->find_similar( $query_embedding, $rows, $limit );
85 return wp_list_pluck( $results, 'post_id' );
86 }
87
88 /**
89 * Find the most semantically similar embeddings using cosine similarity.
90 *
91 * Optimized to reduce redundant computations and use a dedicated
92 * score calculation method for better maintainability.
93 *
94 * @param array $embedding The query embedding vector to compare.
95 * @param array $rows The stored embedding records from the database.
96 * @param int $limit Optional. Number of top matches to return. Default 5.
97 *
98 * @return array List of matched items with product ID, title, and similarity score.
99 */
100 public function find_similar( array $embedding, array $rows, int $limit = 5 ): array {
101 $minimum_match = AIFns::get_embedding_minimum_accuracy();
102 $query_norm = sqrt( array_sum( array_map( static fn( $x ) => $x * $x, $embedding ) ) );
103 $matches = [];
104 foreach ( $rows as $row ) {
105 if ( empty( $row['embedding'] ) ) {
106 continue;
107 }
108 $vector = maybe_unserialize( $row['embedding'] );
109 if ( ! is_array( $vector ) ) {
110 continue;
111 }
112 // Avoid redundant norm computation if similarity calc includes it.
113 $score = $this->calculate_similarity_score( $embedding, $vector, $query_norm );
114 if ( $score >= $minimum_match ) {
115 $matches[] = [
116 'post_id' => isset( $row['product_id'] ) ? (int) $row['product_id'] : 0,
117 'post_title' => isset( $row['title'] ) ? sanitize_text_field( $row['title'] ) : '',
118 'score' => round( $score, 4 ),
119 ];
120 }
121 }
122 if ( empty( $matches ) ) {
123 return [];
124 }
125 // Use array_multisort for faster sorting on large datasets.
126 array_multisort( array_column( $matches, 'score' ), SORT_DESC, $matches );
127 return $limit > 0 ? array_slice( $matches, 0, $limit ) : $matches;
128 }
129
130
131 /**
132 * Calculate cosine similarity score between query and stored embedding.
133 *
134 * This version avoids redundant normalization by reusing the precomputed
135 * query norm and computes the dot product and target norm in one pass.
136 *
137 * @param array $query_vec The query embedding vector.
138 * @param array $target_vec The stored embedding vector.
139 * @param float $query_norm Precomputed L2 norm of the query vector.
140 *
141 * @return float Cosine similarity score (0.0–1.0).
142 */
143 protected function calculate_similarity_score( array $query_vec, array $target_vec, float $query_norm ): float {
144 $dot = 0.0;
145 $normB = 0.0;
146 $len = min( count( $query_vec ), count( $target_vec ) );
147 for ( $i = 0; $i < $len; $i++ ) {
148 $dot += $query_vec[ $i ] * $target_vec[ $i ];
149 $normB += $target_vec[ $i ] ** 2;
150 }
151 if ( $query_norm <= 0.0 || $normB <= 0.0 ) {
152 return 0.0;
153 }
154 return $dot / ( $query_norm * sqrt( $normB ) );
155 }
156 }
157