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

152 lines 4.8 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 $text = $title . ' ' . wp_strip_all_tags( $content );
46 $ai_service = AIFns::initializeAIService();
47 $embedding = $ai_service->generateEmbedding( $text );
48 if ( empty( $embedding ) || ! is_array( $embedding ) ) {
49 return false;
50 }
51 $info = [
52 'word_count' => str_word_count( $text ),
53 'source' => 'product',
54 ];
55 $result = AIDB::upsert_embeding( $product_id, $title, $embedding, $info );
56 return ! empty( $result );
57 }
58
59 /**
60 * Perform a semantic search based on a given query.
61 *
62 * @param string $query The user search query.
63 * @param int $limit Optional. Number of results to return. Default 0 (all).
64 *
65 * @return array List of matching product titles.
66 */
67 public function search( $query, $limit = 0 ) {
68 $ai_service = AIFns::initializeAIService();
69 $query_embedding = $ai_service->generateEmbedding( $query );
70 if ( empty( $query_embedding ) || ! is_array( $query_embedding ) ) {
71 return [];
72 }
73 // Log query embedding.
74 $rows = AIDB::get_all();
75
76 if ( empty( $rows ) ) {
77 return [];
78 }
79 $results = $this->find_similar( $query_embedding, $rows, $limit );
80 return wp_list_pluck( $results, 'post_id' );
81 }
82
83 /**
84 * Find the most semantically similar embeddings using cosine similarity.
85 *
86 * Optimized to reduce redundant computations and use a dedicated
87 * score calculation method for better maintainability.
88 *
89 * @param array $embedding The query embedding vector to compare.
90 * @param array $rows The stored embedding records from the database.
91 * @param int $limit Optional. Number of top matches to return. Default 5.
92 *
93 * @return array List of matched items with product ID, title, and similarity score.
94 */
95 public function find_similar( array $embedding, array $rows, int $limit = 5 ): array {
96 $minimum_match = AIFns::get_embedding_minimum_accuracy();
97 $query_norm = sqrt( array_sum( array_map( static fn( $x ) => $x * $x, $embedding ) ) );
98 $matches = [];
99 foreach ( $rows as $row ) {
100 if ( empty( $row['embedding'] ) ) {
101 continue;
102 }
103 $vector = maybe_unserialize( $row['embedding'] );
104 if ( ! is_array( $vector ) ) {
105 continue;
106 }
107 // Avoid redundant norm computation if similarity calc includes it.
108 $score = $this->calculate_similarity_score( $embedding, $vector, $query_norm );
109 if ( $score >= $minimum_match ) {
110 $matches[] = [
111 'post_id' => isset( $row['product_id'] ) ? (int) $row['product_id'] : 0,
112 'post_title' => isset( $row['title'] ) ? sanitize_text_field( $row['title'] ) : '',
113 'score' => round( $score, 4 ),
114 ];
115 }
116 }
117 if ( empty( $matches ) ) {
118 return [];
119 }
120 // Use array_multisort for faster sorting on large datasets.
121 array_multisort( array_column( $matches, 'score' ), SORT_DESC, $matches );
122 return $limit > 0 ? array_slice( $matches, 0, $limit ) : $matches;
123 }
124
125
126 /**
127 * Calculate cosine similarity score between query and stored embedding.
128 *
129 * This version avoids redundant normalization by reusing the precomputed
130 * query norm and computes the dot product and target norm in one pass.
131 *
132 * @param array $query_vec The query embedding vector.
133 * @param array $target_vec The stored embedding vector.
134 * @param float $query_norm Precomputed L2 norm of the query vector.
135 *
136 * @return float Cosine similarity score (0.0–1.0).
137 */
138 protected function calculate_similarity_score( array $query_vec, array $target_vec, float $query_norm ): float {
139 $dot = 0.0;
140 $normB = 0.0;
141 $len = min( count( $query_vec ), count( $target_vec ) );
142 for ( $i = 0; $i < $len; $i++ ) {
143 $dot += $query_vec[ $i ] * $target_vec[ $i ];
144 $normB += $target_vec[ $i ] ** 2;
145 }
146 if ( $query_norm <= 0.0 || $normB <= 0.0 ) {
147 return 0.0;
148 }
149 return $dot / ( $query_norm * sqrt( $normB ) );
150 }
151 }
152