PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.5.7
MxChat – AI Chatbot & Content Generation for WordPress v2.5.7
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
← All changes | includes/class-mxchat-utils.php +483 -71 2.0.42.5.7 View file →
@@ -4,112 +4,524 @@
4 4 }
5 5
6 6 class MxChat_Utils {
7 7
8 - /**
9 - * Submit or update content (and its embedding) in the database.
10 - *
11 - * If the source_url already exists, update that row.
12 - * Otherwise, insert a new row.
13 - *
14 - * @param string $content The content to be embedded.
15 - * @param string $source_url The source URL of the content.
16 - * @param string $api_key The API key used for generating embeddings.
17 - */
18 - public static function submit_content_to_db($content, $source_url, $api_key) {
19 - global $wpdb;
20 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
8 +/**
9 + * UPDATED: Submit or update content (and its embedding) in the database.
10 + * Stores in Pinecone if enabled, otherwise stores in WordPress DB.
11 + *
12 + * @param string $content The content to be embedded.
13 + * @param string $source_url The source URL of the content.
14 + * @param string $api_key The API key used for generating embeddings.
15 + * @param string $vector_id Optional vector ID for Pinecone (if not provided, will use md5 of URL)
16 + * @param string $bot_id The bot ID for multi-bot support
17 + * @param string $content_type The type of content (post, page, pdf, url, manual, product, etc.)
18 + * @return bool|WP_Error True on success, WP_Error on failure
19 + */
20 +public static function submit_content_to_db($content, $source_url, $api_key, $vector_id = null, $bot_id = 'default', $content_type = 'content') {
21 + global $wpdb;
22 + $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
21 23
22 - // Sanitize the source URL
23 - $source_url = esc_url_raw($source_url);
24 + //error_log('[MXCHAT-DB] Starting database submission for URL: ' . $source_url . ' (Bot: ' . $bot_id . ', Type: ' . $content_type . ')');
25 + //error_log('[MXCHAT-DB] Content length: ' . strlen($content) . ' bytes');
24 26
25 - // Generate the embedding using the API key
26 - $embedding_vector = self::generate_embedding($content, $api_key);
27 + // Sanitize the source URL
28 + $source_url = esc_url_raw($source_url);
27 29
28 - if (is_array($embedding_vector)) {
29 - $embedding_vector_serialized = maybe_serialize($embedding_vector);
30 + // Sanitize content_type
31 + $content_type = sanitize_key($content_type);
32 + if (empty($content_type)) {
33 + $content_type = 'content'; // Fallback for backwards compatibility
34 + }
30 35
31 - // Check if a row already exists for this URL
32 - $existing_id = $wpdb->get_var(
33 - $wpdb->prepare(
34 - "SELECT id FROM {$table_name} WHERE source_url = %s LIMIT 1",
35 - $source_url
36 - )
37 - );
36 + // Just ensure UTF-8 validity without aggressive escaping
37 + $safe_content = wp_check_invalid_utf8($content);
38 + // Remove only null bytes and other control characters, but preserve newlines (\n = \x0A) and carriage returns (\r = \x0D)
39 + $safe_content = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $safe_content);
38 40
41 + // UPDATED: Generate the embedding using bot-specific configuration
42 + $embedding_vector = self::generate_embedding($content, $api_key, $bot_id);
43 +
44 + if (!is_array($embedding_vector)) {
45 + //error_log('[MXCHAT-DB] Error: Embedding generation failed');
46 + return new WP_Error('embedding_failed', 'Failed to generate embedding for content');
47 + }
48 +
49 + //error_log('[MXCHAT-DB] Embedding generated successfully');
50 +
51 + // UPDATED: Check if Pinecone is enabled for this specific bot
52 + if (self::is_pinecone_enabled_for_bot($bot_id)) {
53 + //error_log('[MXCHAT-DB] Pinecone is enabled for bot ' . $bot_id . ' - using Pinecone storage');
54 + // Store in Pinecone only
55 + return self::store_in_pinecone_only($embedding_vector, $content, $source_url, $vector_id, $bot_id, $content_type);
56 + } else {
57 + //error_log('[MXCHAT-DB] Pinecone not enabled for bot ' . $bot_id . ' - using WordPress storage');
58 + // Store in WordPress database only
59 + $embedding_vector_serialized = maybe_serialize($embedding_vector);
60 + return self::store_in_wordpress_db($safe_content, $source_url, $embedding_vector_serialized, $table_name, $content_type);
61 + }
62 +}
63 +
64 +/**
65 + * UPDATED: Check if Pinecone is enabled and properly configured for a specific bot
66 + */
67 +private static function is_pinecone_enabled_for_bot($bot_id = 'default') {
68 + // For default bot or when multi-bot is not active, use original method
69 + if ($bot_id === 'default' || !class_exists('MxChat_Multi_Bot_Manager')) {
70 + return self::is_pinecone_enabled();
71 + }
72 +
73 + // Get bot-specific Pinecone configuration
74 + $bot_pinecone_config = apply_filters('mxchat_get_bot_pinecone_config', array(), $bot_id);
75 +
76 + if (empty($bot_pinecone_config)) {
77 + // Fallback to default configuration
78 + return self::is_pinecone_enabled();
79 + }
80 +
81 + $enabled_check = !empty($bot_pinecone_config['use_pinecone']) && $bot_pinecone_config['use_pinecone'];
82 + $api_key_check = !empty($bot_pinecone_config['api_key']);
83 + $host_check = !empty($bot_pinecone_config['host']);
84 +
85 + return $enabled_check && $api_key_check && $host_check;
86 +}
87 +
88 +/**
89 + * Check if Pinecone is enabled and properly configured (original method for default bot)
90 + */
91 +private static function is_pinecone_enabled() {
92 + $pinecone_options = get_option('mxchat_pinecone_addon_options');
93 +
94 + if (empty($pinecone_options)) {
95 + return false;
96 + }
97 +
98 + $enabled_check = !empty($pinecone_options['mxchat_use_pinecone']) && $pinecone_options['mxchat_use_pinecone'] !== '0';
99 + $api_key_check = !empty($pinecone_options['mxchat_pinecone_api_key']);
100 + $host_check = !empty($pinecone_options['mxchat_pinecone_host']);
101 +
102 + return $enabled_check && $api_key_check && $host_check;
103 +}
104 +
105 +/**
106 + * UPDATED: Store content in Pinecone only with bot support
107 + */
108 +private static function store_in_pinecone_only($embedding_vector, $content, $source_url, $vector_id = null, $bot_id = 'default', $content_type = 'content') {
109 + //error_log('[MXCHAT-PINECONE] ===== Using Pinecone-only storage for bot ' . $bot_id . ' =====');
110 +
111 + // Get bot-specific Pinecone configuration
112 + if ($bot_id === 'default' || !class_exists('MxChat_Multi_Bot_Manager')) {
113 + $pinecone_options = get_option('mxchat_pinecone_addon_options');
114 + $api_key = $pinecone_options['mxchat_pinecone_api_key'];
115 + $environment = $pinecone_options['mxchat_pinecone_environment'] ?? '';
116 + $index_name = $pinecone_options['mxchat_pinecone_index'] ?? '';
117 + $namespace = $pinecone_options['mxchat_pinecone_namespace'] ?? '';
118 + } else {
119 + $bot_pinecone_config = apply_filters('mxchat_get_bot_pinecone_config', array(), $bot_id);
120 + if (empty($bot_pinecone_config)) {
121 + // Fallback to default configuration
122 + $pinecone_options = get_option('mxchat_pinecone_addon_options');
123 + $api_key = $pinecone_options['mxchat_pinecone_api_key'];
124 + $environment = $pinecone_options['mxchat_pinecone_environment'] ?? '';
125 + $index_name = $pinecone_options['mxchat_pinecone_index'] ?? '';
126 + $namespace = $pinecone_options['mxchat_pinecone_namespace'] ?? '';
127 + } else {
128 + $api_key = $bot_pinecone_config['api_key'];
129 + $environment = ''; // Not used in new Pinecone API
130 + $index_name = ''; // Not used in new Pinecone API
131 + $namespace = $bot_pinecone_config['namespace'] ?? '';
132 + }
133 + }
134 +
135 + $result = self::store_in_pinecone_main(
136 + $embedding_vector,
137 + $content,
138 + $source_url,
139 + $api_key,
140 + $environment,
141 + $index_name,
142 + $vector_id,
143 + $bot_id,
144 + $namespace,
145 + $content_type
146 + );
147 +
148 + if (is_wp_error($result)) {
149 + //error_log('[MXCHAT-PINECONE] Pinecone storage failed for bot ' . $bot_id . ': ' . $result->get_error_message());
150 + return $result;
151 + }
152 +
153 + //error_log('[MXCHAT-PINECONE] Pinecone storage completed successfully for bot ' . $bot_id);
154 + return true;
155 +}
156 +
157 +/**
158 + * Store content in WordPress database with progressive fallback
159 + * UPDATED 2.5.6: Now includes content_type parameter
160 + */
161 +private static function store_in_wordpress_db($safe_content, $source_url, $embedding_vector_serialized, $table_name, $content_type = 'content') {
162 + global $wpdb;
163 +
164 + //error_log('[MXCHAT-DB] ===== Using WordPress-only storage =====');
165 +
166 + // Sanitize content_type
167 + $content_type = sanitize_key($content_type);
168 + if (empty($content_type)) {
169 + $content_type = 'content'; // Fallback for backwards compatibility
170 + }
171 +
172 + // ===== FIXED: Generate unique identifier for manual content =====
173 + $original_source_url = $source_url;
174 + $is_manual_content = empty($source_url) || $source_url === '' || !filter_var($source_url, FILTER_VALIDATE_URL);
175 +
176 + if ($is_manual_content) {
177 + // Generate unique identifier for manual content to prevent overwrites
178 + $source_url = 'mxchat://manual-content/' . time() . '-' . wp_generate_password(8, false);
179 + //error_log('[MXCHAT-DB] Generated unique ID for manual content: ' . $source_url);
180 + }
181 +
182 + // Only check for duplicates if we have a valid source URL (not manual content)
183 + $existing_id = null;
184 + if (!$is_manual_content) {
185 + $existing_id = $wpdb->get_var(
186 + $wpdb->prepare(
187 + "SELECT id FROM {$table_name} WHERE source_url = %s LIMIT 1",
188 + $source_url
189 + )
190 + );
191 + //error_log('[MXCHAT-DB] Checked for existing URL, found ID: ' . ($existing_id ?: 'none'));
192 + } else {
193 + //error_log('[MXCHAT-DB] Manual content - will create new entry (no duplicate check)');
194 + }
195 + // ===== END FIX =====
196 +
197 + // Progressive fallback mechanism for problematic content
198 + $attempt = 1;
199 + $max_attempts = 3;
200 + $current_content = $safe_content;
201 + $result = false;
202 +
203 + while ($attempt <= $max_attempts && $result === false) {
204 + try {
39 205 if ($existing_id) {
40 - // Update the existing row
41 - $wpdb->update(
206 + //error_log('[MXCHAT-DB] Found existing entry (ID: ' . $existing_id . '). Updating... (Attempt ' . $attempt . ')');
207 +
208 + // Update the existing row - UPDATED 2.5.6: Added content_type
209 + $result = $wpdb->update(
42 210 $table_name,
43 211 array(
44 - 'article_content' => $content,
212 + 'url' => $source_url,
213 + 'article_content' => $current_content,
45 214 'embedding_vector' => $embedding_vector_serialized,
46 - 'timestamp' => current_time('mysql'), // Remove if your table doesn't have a timestamp column
215 + 'source_url' => $source_url,
216 + 'content_type' => $content_type,
217 + 'timestamp' => current_time('mysql'),
47 218 ),
48 219 array('id' => $existing_id),
49 - array('%s','%s','%s'),
220 + array('%s','%s','%s','%s','%s','%s'),
50 221 array('%d')
51 222 );
52 223 } else {
53 - // Insert a new row
54 - $wpdb->insert(
224 + //error_log('[MXCHAT-DB] No existing entry found. Inserting new row... (Attempt ' . $attempt . ')');
225 + //error_log('[MXCHAT-DB] Content sample: ' . substr($current_content, 0, 1000));
226 +
227 + // Insert a new row - UPDATED 2.5.6: Added content_type
228 + $result = $wpdb->insert(
55 229 $table_name,
56 230 array(
57 - 'article_content' => $content,
231 + 'url' => $source_url, // Now unique for manual content
232 + 'article_content' => $current_content,
58 233 'embedding_vector' => $embedding_vector_serialized,
59 - 'source_url' => $source_url,
60 - 'timestamp' => current_time('mysql'), // Remove if your table doesn't have a timestamp column
234 + 'source_url' => $source_url, // Now unique for manual content
235 + 'content_type' => $content_type,
236 + 'timestamp' => current_time('mysql'),
61 237 ),
62 - array('%s','%s','%s','%s')
238 + array('%s','%s','%s','%s','%s','%s')
63 239 );
64 240 }
241 +
242 + if ($result === false) {
243 + error_log('[MXCHAT-DB] Database operation failed (Attempt ' . $attempt . ')');
244 + error_log('[MXCHAT-DB] MySQL Error: ' . $wpdb->last_error);
245 + error_log('[MXCHAT-DB] MySQL Error Number: ' . $wpdb->last_errno);
246 + error_log('[MXCHAT-DB] Last Query: ' . substr($wpdb->last_query, 0, 500));
247 + error_log('[MXCHAT-DB] Content length: ' . strlen($current_content) . ' bytes');
248 + error_log('[MXCHAT-DB] Embedding vector length: ' . strlen($embedding_vector_serialized) . ' bytes');
249 +
250 + // Progressively apply more aggressive sanitization on failure
251 + if ($attempt === 1) {
252 + // First fallback: Use a more aggressive character filter and shorten
253 + $current_content = preg_replace('/[^\p{L}\p{N}\s.,;:!?()-]/u', '', $current_content);
254 + $current_content = substr($current_content, 0, 50000);
255 + } else if ($attempt === 2) {
256 + // Second fallback: Keep only alphanumeric and basic punctuation, shorten further
257 + $current_content = preg_replace('/[^a-zA-Z0-9\s.,;:!?()-]/u', '', $current_content);
258 + $current_content = substr($current_content, 0, 30000);
259 + }
260 +
261 + $attempt++;
262 + }
263 + } catch (Exception $e) {
264 + //error_log('[MXCHAT-DB] Exception during database operation: ' . $e->getMessage());
265 + $attempt++;
266 + }
267 + }
268 +
269 +if ($result === false) {
270 + error_log('[MXCHAT-DB] All database operation attempts failed');
271 + error_log('[MXCHAT-DB] Final MySQL Error: ' . $wpdb->last_error);
272 +
273 + $detailed_error = sprintf(
274 + 'Failed to store content in WordPress database after %d attempts. MySQL Error: %s (Error #%d). Content size: %d bytes, Embedding size: %d bytes',
275 + $max_attempts,
276 + $wpdb->last_error,
277 + $wpdb->last_errno,
278 + strlen($current_content),
279 + strlen($embedding_vector_serialized)
280 + );
281 +
282 + return new WP_Error('database_failed', $detailed_error);
283 +}
284 +
285 + //error_log('[MXCHAT-DB] WordPress database operation completed successfully (Attempt ' . ($attempt - 1) . ')');
286 + return true;
287 +}
288 +
289 +/**
290 + * UPDATED: Store content in Pinecone database with bot support
291 + * UPDATED 2.5.6: Now accepts content_type parameter
292 + */
293 +private static function store_in_pinecone_main($embedding_vector, $content, $url, $api_key, $environment, $index_name, $vector_id = null, $bot_id = 'default', $namespace = '', $content_type = 'content') {
294 + //error_log('[MXCHAT-PINECONE-MAIN] ===== Starting Pinecone storage for bot ' . $bot_id . ' =====');
295 +
296 + // ===== UPDATED: Handle manual content with unique vector IDs =====
297 + if ($vector_id) {
298 + // Use provided vector ID
299 + //error_log('[MXCHAT-PINECONE-MAIN] Using provided vector ID: ' . $vector_id);
300 + } elseif (!empty($url) && filter_var($url, FILTER_VALIDATE_URL)) {
301 + // For valid URLs, use URL-based ID (existing behavior)
302 + $vector_id = md5($url);
303 + //error_log('[MXCHAT-PINECONE-MAIN] Generated vector ID from URL: ' . $vector_id);
304 + } else {
305 + // For manual content (empty/invalid URL), generate unique ID
306 + $vector_id = 'manual_' . time() . '_' . substr(md5($content . microtime(true)), 0, 8);
307 + //error_log('[MXCHAT-PINECONE-MAIN] Generated unique vector ID for manual content: ' . $vector_id);
308 + }
309 + // ===== END UPDATE =====
310 +
311 + // Get host from bot-specific config or fallback to default
312 + if ($bot_id === 'default' || !class_exists('MxChat_Multi_Bot_Manager')) {
313 + $options = get_option('mxchat_pinecone_addon_options');
314 + $host = $options['mxchat_pinecone_host'] ?? '';
315 + } else {
316 + $bot_pinecone_config = apply_filters('mxchat_get_bot_pinecone_config', array(), $bot_id);
317 + if (!empty($bot_pinecone_config)) {
318 + $host = $bot_pinecone_config['host'] ?? '';
65 319 } else {
66 - // If embedding generation failed, you could log or handle it here
67 - // error_log('Embedding generation failed for content from ' . $source_url);
320 + $options = get_option('mxchat_pinecone_addon_options');
321 + $host = $options['mxchat_pinecone_host'] ?? '';
68 322 }
69 323 }
70 324
71 - /**
72 - * Generate an embedding for the given text using the specified API key.
73 - *
74 - * @param string $text The text to be embedded.
75 - * @param string $api_key The API key used for generating embeddings.
76 - * @return array|null The embedding vector or null on failure.
77 - */
78 - private static function generate_embedding($text, $api_key) {
79 - $endpoint = 'https://api.openai.com/v1/embeddings';
325 + //error_log('[MXCHAT-PINECONE-MAIN] Host: ' . $host);
326 + //error_log('[MXCHAT-PINECONE-MAIN] API key length: ' . strlen($api_key));
327 + //error_log('[MXCHAT-PINECONE-MAIN] Bot ID: ' . $bot_id);
328 + //error_log('[MXCHAT-PINECONE-MAIN] Namespace: ' . $namespace);
80 329
81 - $body = wp_json_encode([
82 - 'input' => $text,
83 - 'model' => 'text-embedding-ada-002'
84 - ]);
330 + if (empty($host)) {
331 + //error_log('[MXCHAT-PINECONE-MAIN] ERROR: Host is empty');
332 + return new WP_Error('pinecone_config', 'Pinecone host is not configured. Please set the host in your bot settings.');
333 + }
85 334
86 - $args = [
87 - 'body' => $body,
88 - 'headers' => [
89 - 'Content-Type' => 'application/json',
90 - 'Authorization' => 'Bearer ' . $api_key,
335 + // ===== UPDATED 2.5.6: Use passed content_type or determine from URL if not provided =====
336 + // Sanitize content_type
337 + $content_type = sanitize_key($content_type);
338 + if (empty($content_type)) {
339 + // Fallback to old detection logic for backwards compatibility
340 + $is_product = false;
341 + $content_type = 'manual'; // Default for manual content
342 +
343 + if (!empty($url) && filter_var($url, FILTER_VALIDATE_URL)) {
344 + $is_product = (strpos($url, '/product/') !== false || strpos($url, '/shop/') !== false);
345 + $content_type = $is_product ? 'product' : 'content';
346 + }
347 + }
348 +
349 + //error_log('[MXCHAT-PINECONE-MAIN] Content type: ' . $content_type);
350 + // ===== END UPDATE =====
351 +
352 + $api_endpoint = "https://{$host}/vectors/upsert";
353 + //error_log('[MXCHAT-PINECONE-MAIN] API endpoint: ' . $api_endpoint);
354 +
355 + // UPDATED 2.5.6: Use provided content_type in metadata
356 + $metadata = array(
357 + 'text' => $content,
358 + 'source_url' => $url, // Can be empty for manual content
359 + 'type' => $content_type, // Now supports: post, page, pdf, url, manual, product, etc.
360 + 'last_updated' => time(),
361 + 'created_at' => time(), // Add creation timestamp
362 + 'bot_id' => $bot_id // Add bot identification
363 + );
364 +
365 + $vector_data = array(
366 + 'id' => $vector_id,
367 + 'values' => $embedding_vector,
368 + 'metadata' => $metadata
369 + );
370 +
371 + $request_body = array(
372 + 'vectors' => array($vector_data)
373 + );
374 +
375 + // Add namespace if specified for multi-bot separation
376 + if (!empty($namespace)) {
377 + $request_body['namespace'] = $namespace;
378 + //error_log('[MXCHAT-PINECONE-MAIN] Using namespace: ' . $namespace);
379 + }
380 +
381 + //error_log('[MXCHAT-PINECONE-MAIN] Request body prepared (embedding dimensions: ' . count($embedding_vector) . ')');
382 +
383 + $response = wp_remote_post($api_endpoint, array(
384 + 'headers' => array(
385 + 'Api-Key' => $api_key,
386 + 'accept' => 'application/json',
387 + 'content-type' => 'application/json'
388 + ),
389 + 'body' => wp_json_encode($request_body),
390 + 'timeout' => 30,
391 + 'data_format' => 'body'
392 + ));
393 +
394 + if (is_wp_error($response)) {
395 + //error_log('[MXCHAT-PINECONE-MAIN] WordPress request error: ' . $response->get_error_message());
396 + return new WP_Error('pinecone_request', $response->get_error_message());
397 + }
398 +
399 + $response_code = wp_remote_retrieve_response_code($response);
400 + //error_log('[MXCHAT-PINECONE-MAIN] Response code: ' . $response_code);
401 +
402 + if ($response_code !== 200) {
403 + $body = wp_remote_retrieve_body($response);
404 + //error_log('[MXCHAT-PINECONE-MAIN] API error - Response body: ' . $body);
405 + return new WP_Error('pinecone_api', sprintf(
406 + 'Pinecone API error (HTTP %d): %s',
407 + $response_code,
408 + $body
409 + ));
410 + }
411 +
412 + $response_body = wp_remote_retrieve_body($response);
413 + //error_log('[MXCHAT-PINECONE-MAIN] Success response: ' . $response_body);
414 + //error_log('[MXCHAT-PINECONE-MAIN] Successfully stored in Pinecone for bot ' . $bot_id);
415 + //error_log('[MXCHAT-PINECONE-MAIN] ===== Pinecone storage complete =====');
416 +
417 + return true;
418 +}
419 +
420 +/**
421 + * UPDATED: Generate an embedding for the given text using bot-specific configuration.
422 + *
423 + * @param string $text The text to be embedded.
424 + * @param string $api_key The API key used for generating embeddings.
425 + * @param string $bot_id The bot ID for multi-bot support
426 + * @return array|null The embedding vector or null on failure.
427 + */
428 +private static function generate_embedding($text, $api_key, $bot_id = 'default') {
429 + // Get bot-specific options
430 + if ($bot_id === 'default' || !class_exists('MxChat_Multi_Bot_Manager')) {
431 + $options = get_option('mxchat_options');
432 + } else {
433 + $bot_options = apply_filters('mxchat_get_bot_options', array(), $bot_id);
434 + $options = !empty($bot_options) ? $bot_options : get_option('mxchat_options');
435 + }
436 +
437 + $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
438 +
439 + // Determine endpoint and API key based on model
440 + if (strpos($selected_model, 'voyage') === 0) {
441 + $endpoint = 'https://api.voyageai.com/v1/embeddings';
442 + $api_key = $options['voyage_api_key'] ?? '';
443 + } elseif (strpos($selected_model, 'gemini-embedding') === 0) {
444 + $endpoint = 'https://generativelanguage.googleapis.com/v1beta/models/' . $selected_model . ':embedContent';
445 + $api_key = $options['gemini_api_key'] ?? '';
446 + } else {
447 + $endpoint = 'https://api.openai.com/v1/embeddings';
448 + // Use the bot-specific API key or fallback to passed API key
449 + $api_key = $options['api_key'] ?? $api_key;
450 + }
451 +
452 + // Prepare request body based on provider
453 + if (strpos($selected_model, 'gemini-embedding') === 0) {
454 + // Gemini API format
455 + $request_body = [
456 + 'model' => 'models/' . $selected_model,
457 + 'content' => [
458 + 'parts' => [
459 + ['text' => $text]
460 + ]
91 461 ],
92 - 'timeout' => 60,
93 - 'redirection' => 5,
94 - 'blocking' => true,
95 - 'httpversion' => '1.0',
96 - 'sslverify' => true,
462 + 'outputDimensionality' => 1536
97 463 ];
98 -
99 - $response = wp_remote_post($endpoint, $args);
100 -
101 - if (is_wp_error($response)) {
102 - // error_log('Error generating embedding: ' . $response->get_error_message());
464 +
465 + // Prepare headers for Gemini (API key as query parameter)
466 + $endpoint .= '?key=' . $api_key;
467 + $headers = [
468 + 'Content-Type' => 'application/json'
469 + ];
470 + } else {
471 + // OpenAI/Voyage API format
472 + $request_body = [
473 + 'input' => $text,
474 + 'model' => $selected_model
475 + ];
476 +
477 + // Add output_dimension for voyage-3-large
478 + if ($selected_model === 'voyage-3-large') {
479 + $request_body['output_dimension'] = 2048;
480 + }
481 +
482 + // Prepare headers for OpenAI/Voyage
483 + $headers = [
484 + 'Content-Type' => 'application/json',
485 + 'Authorization' => 'Bearer ' . $api_key
486 + ];
487 + }
488 +
489 + $args = [
490 + 'body' => wp_json_encode($request_body),
491 + 'headers' => $headers,
492 + 'timeout' => 60,
493 + 'redirection' => 5,
494 + 'blocking' => true,
495 + 'httpversion' => '1.0',
496 + 'sslverify' => true,
497 + ];
498 +
499 + $response = wp_remote_post($endpoint, $args);
500 +
501 + if (is_wp_error($response)) {
502 + //error_log('Error generating embedding for bot ' . $bot_id . ': ' . $response->get_error_message());
503 + return null;
504 + }
505 +
506 + $response_body = json_decode(wp_remote_retrieve_body($response), true);
507 +
508 + // Handle different response formats based on provider
509 + if (strpos($selected_model, 'gemini-embedding') === 0) {
510 + // Gemini API response format
511 + if (isset($response_body['embedding']['values']) && is_array($response_body['embedding']['values'])) {
512 + return $response_body['embedding']['values'];
513 + } else {
514 + //error_log('Invalid response received from Gemini embedding API for bot ' . $bot_id . ': ' . wp_json_encode($response_body));
103 515 return null;
104 516 }
105 -
106 - $response_body = json_decode(wp_remote_retrieve_body($response), true);
107 -
517 + } else {
518 + // OpenAI/Voyage API response format
108 519 if (isset($response_body['data'][0]['embedding']) && is_array($response_body['data'][0]['embedding'])) {
109 520 return $response_body['data'][0]['embedding'];
110 521 } else {
111 - // error_log('Invalid response received from embedding API.');
522 + //error_log('Invalid response received from embedding API for bot ' . $bot_id . ': ' . wp_json_encode($response_body));
112 523 return null;
113 524 }
114 525 }
115 526 }
527 +}