PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.2.3
MxChat – AI Chatbot & Content Generation for WordPress v2.2.3
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
mxchat-basic / admin / class-pinecone-manager.php

class-pinecone-manager.php in MxChat – AI Chatbot & Content Generation for WordPress 2.2.3, at admin/class-pinecone-manager.php

999 lines 36.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * File: admin/class-pinecone-manager.php
4 *
5 * Handles all Pinecone vector database operations for MxChat
6 */
7
8 if (!defined('ABSPATH')) {
9 exit; // Exit if accessed directly
10 }
11
12 class MxChat_Pinecone_Manager {
13
14 /**
15 * Constructor
16 */
17 public function __construct() {
18 // Hook into WordPress actions if needed
19 add_action('mxchat_delete_content', array($this, 'mxchat_delete_from_pinecone_by_url'), 10, 1);
20 }
21
22 // ========================================
23 // PINECONE FETCH OPERATIONS
24 // ========================================
25
26 /**
27 * Fetches records from Pinecone using provided options
28 */
29 public function mxchat_fetch_pinecone_records($pinecone_options, $search_query = '', $page = 1, $per_page = 20) {
30 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
31 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
32 $index = $pinecone_options['mxchat_pinecone_index'] ?? '';
33
34 if (empty($api_key) || empty($host) || empty($index)) {
35 return array('data' => array(), 'total' => 0);
36 }
37
38 try {
39 // First, try to get index statistics to understand the data structure
40 $stats_url = "https://{$host}/describe_index_stats";
41
42 $stats_response = wp_remote_post($stats_url, array(
43 'headers' => array(
44 'Api-Key' => $api_key,
45 'Content-Type' => 'application/json'
46 ),
47 'body' => json_encode(array()),
48 'timeout' => 30
49 ));
50
51 // Check if we can get vector IDs from local cache first
52 $cached_vector_ids = get_option('mxchat_pinecone_vector_ids_cache', array());
53
54 $all_records = array();
55
56 // Method 1: Use cached vector IDs to fetch specific vectors
57 if (!empty($cached_vector_ids)) {
58 $all_records = $this->mxchat_fetch_vectors_by_ids($pinecone_options, $cached_vector_ids);
59 }
60
61 // Method 2: Fallback to scanning approach if cache is empty or incomplete
62 if (empty($all_records)) {
63 $all_records = $this->mxchat_scan_pinecone_vectors($pinecone_options);
64 }
65
66 // Filter by search query if provided
67 if (!empty($search_query)) {
68 $all_records = array_filter($all_records, function($record) use ($search_query) {
69 $content = $record->article_content ?? '';
70 $source_url = $record->source_url ?? '';
71 return stripos($content, $search_query) !== false ||
72 stripos($source_url, $search_query) !== false;
73 });
74 }
75
76 // Sort records by created_at in descending order (newest first)
77 usort($all_records, function($a, $b) {
78 $time_a = is_numeric($a->created_at) ? $a->created_at : strtotime($a->created_at);
79 $time_b = is_numeric($b->created_at) ? $b->created_at : strtotime($b->created_at);
80 return $time_b - $time_a;
81 });
82
83 // Handle pagination
84 $total = count($all_records);
85 $offset = ($page - 1) * $per_page;
86 $paged_records = array_slice($all_records, $offset, $per_page);
87
88 return array(
89 'data' => $paged_records,
90 'total' => $total
91 );
92
93 } catch (Exception $e) {
94 //error_log('Pinecone fetch exception: ' . $e->getMessage());
95 return array('data' => array(), 'total' => 0);
96 }
97 }
98
99
100 /**
101 * Fetches vectors from Pinecone using provided IDs
102 */
103 public function mxchat_fetch_vectors_by_ids($pinecone_options, $vector_ids) {
104 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
105 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
106
107 if (empty($api_key) || empty($host) || empty($vector_ids)) {
108 return array();
109 }
110
111 try {
112 $fetch_url = "https://{$host}/vectors/fetch";
113
114 // Pinecone fetch API allows fetching specific vectors by ID
115 $fetch_data = array(
116 'ids' => array_values($vector_ids)
117 );
118
119 $response = wp_remote_post($fetch_url, array(
120 'headers' => array(
121 'Api-Key' => $api_key,
122 'Content-Type' => 'application/json'
123 ),
124 'body' => json_encode($fetch_data),
125 'timeout' => 30
126 ));
127
128 if (is_wp_error($response)) {
129 //error_log('Pinecone fetch by IDs error: ' . $response->get_error_message());
130 return array();
131 }
132
133 $response_code = wp_remote_retrieve_response_code($response);
134 if ($response_code !== 200) {
135 //error_log('Pinecone fetch by IDs failed with code: ' . $response_code);
136 return array();
137 }
138
139 $body = wp_remote_retrieve_body($response);
140 $data = json_decode($body, true);
141
142 if (!isset($data['vectors'])) {
143 return array();
144 }
145
146 $converted_records = array();
147 foreach ($data['vectors'] as $vector_id => $vector_data) {
148 $metadata = $vector_data['metadata'] ?? array();
149
150 $converted_records[] = (object) array(
151 'id' => $vector_id,
152 'article_content' => $metadata['text'] ?? '',
153 'source_url' => $metadata['source_url'] ?? '',
154 'created_at' => $metadata['created_at'] ?? $metadata['last_updated'] ?? time(),
155 'data_source' => 'pinecone'
156 );
157 }
158
159 return $converted_records;
160
161 } catch (Exception $e) {
162 //error_log('Pinecone fetch by IDs exception: ' . $e->getMessage());
163 return array();
164 }
165 }
166
167
168 /**
169 * Fetches vectors from Pinecone using provided IDs (duplicate method)
170 */
171 public function fetch_pinecone_vectors_by_ids($pinecone_options, $vector_ids) {
172 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
173 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
174
175 if (empty($api_key) || empty($host) || empty($vector_ids)) {
176 return array();
177 }
178
179 try {
180 $fetch_url = "https://{$host}/vectors/fetch";
181
182 // Pinecone fetch API allows fetching specific vectors by ID
183 $fetch_data = array(
184 'ids' => array_values($vector_ids)
185 );
186
187 $response = wp_remote_post($fetch_url, array(
188 'headers' => array(
189 'Api-Key' => $api_key,
190 'Content-Type' => 'application/json'
191 ),
192 'body' => json_encode($fetch_data),
193 'timeout' => 30
194 ));
195
196 if (is_wp_error($response)) {
197 //error_log('Pinecone fetch by IDs error: ' . $response->get_error_message());
198 return array();
199 }
200
201 $response_code = wp_remote_retrieve_response_code($response);
202 if ($response_code !== 200) {
203 //error_log('Pinecone fetch by IDs failed with code: ' . $response_code);
204 return array();
205 }
206
207 $body = wp_remote_retrieve_body($response);
208 $data = json_decode($body, true);
209
210 if (!isset($data['vectors'])) {
211 return array();
212 }
213
214 $processed_data = array();
215
216 foreach ($data['vectors'] as $vector_id => $vector_data) {
217 $metadata = $vector_data['metadata'] ?? array();
218 $source_url = $metadata['source_url'] ?? '';
219
220 if (!empty($source_url)) {
221 $post_id = url_to_postid($source_url);
222 if ($post_id) {
223 $created_at = $metadata['created_at'] ?? '';
224 $processed_date = 'Recently'; // Default
225
226 if (!empty($created_at)) {
227 $timestamp = is_numeric($created_at) ? $created_at : strtotime($created_at);
228 if ($timestamp) {
229 $processed_date = human_time_diff($timestamp, current_time('timestamp')) . ' ago';
230 }
231 }
232
233 $processed_data[$post_id] = array(
234 'db_id' => $vector_id,
235 'processed_date' => $processed_date,
236 'url' => $source_url,
237 'source' => 'pinecone',
238 'timestamp' => $timestamp ?? current_time('timestamp')
239 );
240 }
241 }
242 }
243
244 return $processed_data;
245
246 } catch (Exception $e) {
247 //error_log('Pinecone fetch by IDs exception: ' . $e->getMessage());
248 return array();
249 }
250 }
251
252
253 // ========================================
254 // PINECONE SCAN & SEARCH OPERATIONS
255 // ========================================
256
257 /**
258 * Validates API credentials and returns empty array if invalid
259 */
260 private function mxchat_scan_pinecone_vectors($pinecone_options) {
261 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
262 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
263
264 if (empty($api_key) || empty($host)) {
265 return array();
266 }
267
268 try {
269 // Instead of using a dummy zero vector, try multiple random vectors
270 // to get better coverage of the vector space
271 $all_matches = array();
272 $seen_ids = array();
273
274 // Try 3-5 different random vectors to get better coverage
275 for ($i = 0; $i < 3; $i++) {
276 $query_url = "https://{$host}/query";
277
278 // Generate a random unit vector instead of zeros
279 $random_vector = array();
280 for ($j = 0; $j < 1536; $j++) {
281 $random_vector[] = (rand(-1000, 1000) / 1000.0); // Random values between -1 and 1
282 }
283
284 // Normalize the vector to unit length
285 $magnitude = sqrt(array_sum(array_map(function($x) { return $x * $x; }, $random_vector)));
286 if ($magnitude > 0) {
287 $random_vector = array_map(function($x) use ($magnitude) { return $x / $magnitude; }, $random_vector);
288 }
289
290 $query_data = array(
291 'includeMetadata' => true,
292 'includeValues' => false,
293 'topK' => 10000, // Get many results
294 'vector' => $random_vector
295 );
296
297 $response = wp_remote_post($query_url, array(
298 'headers' => array(
299 'Api-Key' => $api_key,
300 'Content-Type' => 'application/json'
301 ),
302 'body' => json_encode($query_data),
303 'timeout' => 30
304 ));
305
306 if (is_wp_error($response)) {
307 continue;
308 }
309
310 $body = wp_remote_retrieve_body($response);
311 $data = json_decode($body, true);
312
313 if (isset($data['matches'])) {
314 foreach ($data['matches'] as $match) {
315 $match_id = $match['id'] ?? '';
316 if (!empty($match_id) && !isset($seen_ids[$match_id])) {
317 $all_matches[] = $match;
318 $seen_ids[$match_id] = true;
319 }
320 }
321 }
322 }
323
324 // Convert matches to records
325 $converted_records = array();
326 $vector_ids_cache = array();
327
328 foreach ($all_matches as $match) {
329 $metadata = $match['metadata'] ?? array();
330 $match_id = $match['id'] ?? '';
331
332 if (!empty($match_id)) {
333 $vector_ids_cache[] = $match_id;
334 }
335
336 $converted_records[] = (object) array(
337 'id' => $match_id,
338 'article_content' => $metadata['text'] ?? '',
339 'source_url' => $metadata['source_url'] ?? '',
340 'created_at' => $metadata['created_at'] ?? $metadata['last_updated'] ?? time(),
341 'data_source' => 'pinecone'
342 );
343 }
344
345 // Update the cache with found vector IDs for future use
346 if (!empty($vector_ids_cache)) {
347 update_option('mxchat_pinecone_vector_ids_cache', $vector_ids_cache);
348 }
349
350 return $converted_records;
351
352 } catch (Exception $e) {
353 //error_log('Pinecone scan exception: ' . $e->getMessage());
354 return array();
355 }
356 }
357
358
359 /**
360 * Validates API key and host, returns empty array if missing
361 */
362 private function mxchat_scan_pinecone_for_processed_content($pinecone_options) {
363 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
364 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
365
366 if (empty($api_key) || empty($host)) {
367 return array();
368 }
369
370 try {
371 // Use multiple random vectors to get better coverage
372 $all_matches = array();
373 $seen_ids = array();
374
375 // Try 3 different random vectors to get better coverage
376 for ($i = 0; $i < 3; $i++) {
377 $query_url = "https://{$host}/query";
378
379 // Generate a random unit vector instead of zeros
380 $random_vector = array();
381 for ($j = 0; $j < 1536; $j++) {
382 $random_vector[] = (rand(-1000, 1000) / 1000.0); // Random values between -1 and 1
383 }
384
385 // Normalize the vector to unit length
386 $magnitude = sqrt(array_sum(array_map(function($x) { return $x * $x; }, $random_vector)));
387 if ($magnitude > 0) {
388 $random_vector = array_map(function($x) use ($magnitude) { return $x / $magnitude; }, $random_vector);
389 }
390
391 $query_data = array(
392 'includeMetadata' => true,
393 'includeValues' => false,
394 'topK' => 10000, // Get many results
395 'vector' => $random_vector
396 );
397
398 $response = wp_remote_post($query_url, array(
399 'headers' => array(
400 'Api-Key' => $api_key,
401 'Content-Type' => 'application/json'
402 ),
403 'body' => json_encode($query_data),
404 'timeout' => 30
405 ));
406
407 if (is_wp_error($response)) {
408 continue;
409 }
410
411 $response_code = wp_remote_retrieve_response_code($response);
412 if ($response_code !== 200) {
413 continue;
414 }
415
416 $body = wp_remote_retrieve_body($response);
417 $data = json_decode($body, true);
418
419 if (isset($data['matches'])) {
420 foreach ($data['matches'] as $match) {
421 $match_id = $match['id'] ?? '';
422 if (!empty($match_id) && !isset($seen_ids[$match_id])) {
423 $all_matches[] = $match;
424 $seen_ids[$match_id] = true;
425 }
426 }
427 }
428 }
429
430 // Convert matches to processed data format
431 $processed_data = array();
432 $vector_ids_for_cache = array();
433
434 foreach ($all_matches as $match) {
435 $metadata = $match['metadata'] ?? array();
436 $source_url = $metadata['source_url'] ?? '';
437 $match_id = $match['id'] ?? '';
438
439 if (!empty($source_url) && !empty($match_id)) {
440 $post_id = url_to_postid($source_url);
441 if ($post_id) {
442 $created_at = $metadata['created_at'] ?? '';
443 $processed_date = 'Recently'; // Default
444
445 if (!empty($created_at)) {
446 $timestamp = is_numeric($created_at) ? $created_at : strtotime($created_at);
447 if ($timestamp) {
448 $processed_date = human_time_diff($timestamp, current_time('timestamp')) . ' ago';
449 }
450 }
451
452 $processed_data[$post_id] = array(
453 'db_id' => $match_id,
454 'processed_date' => $processed_date,
455 'url' => $source_url,
456 'source' => 'pinecone',
457 'timestamp' => $timestamp ?? current_time('timestamp')
458 );
459
460 $vector_ids_for_cache[] = $match_id;
461 }
462 }
463 }
464
465 // Update the vector IDs cache for future use
466 if (!empty($vector_ids_for_cache)) {
467 update_option('mxchat_pinecone_vector_ids_cache', $vector_ids_for_cache);
468 }
469
470 return $processed_data;
471
472 } catch (Exception $e) {
473 //error_log('Pinecone scan exception: ' . $e->getMessage());
474 return array();
475 }
476 }
477
478
479 // ========================================
480 // PINECONE DELETE OPERATIONS
481 // ========================================
482
483 /**
484 * Deletes data from Pinecone using provided API credentials
485 */
486 public function mxchat_delete_all_from_pinecone($pinecone_options) {
487 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
488 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
489
490 if (empty($api_key) || empty($host)) {
491 return array(
492 'success' => false,
493 'message' => 'Missing Pinecone API credentials'
494 );
495 }
496
497 try {
498 // First, get all vector IDs
499 $all_vector_ids = array();
500
501 // Try to get from cache first
502 $cached_vector_ids = get_option('mxchat_pinecone_vector_ids_cache', array());
503 if (!empty($cached_vector_ids)) {
504 $all_vector_ids = $cached_vector_ids;
505 } else {
506 // Fallback: scan to get vector IDs
507 $records = $this->mxchat_scan_pinecone_vectors($pinecone_options);
508 foreach ($records as $record) {
509 if (!empty($record->id)) {
510 $all_vector_ids[] = $record->id;
511 }
512 }
513 }
514
515 if (empty($all_vector_ids)) {
516 return array(
517 'success' => true,
518 'message' => 'No vectors found to delete'
519 );
520 }
521
522 // Delete vectors in batches (Pinecone has limits on batch operations)
523 $batch_size = 100;
524 $batches = array_chunk($all_vector_ids, $batch_size);
525 $deleted_count = 0;
526 $failed_batches = 0;
527
528 foreach ($batches as $batch) {
529 $result = $this->mxchat_delete_pinecone_batch($batch, $api_key, $host);
530 if ($result['success']) {
531 $deleted_count += count($batch);
532 } else {
533 $failed_batches++;
534 //error_log('Failed to delete Pinecone batch: ' . $result['message']);
535 }
536 }
537
538 if ($failed_batches > 0) {
539 return array(
540 'success' => false,
541 'message' => sprintf('Deleted %d vectors, but %d batches failed', $deleted_count, $failed_batches)
542 );
543 }
544
545 return array(
546 'success' => true,
547 'message' => "Successfully deleted {$deleted_count} vectors from Pinecone"
548 );
549
550 } catch (Exception $e) {
551 //error_log('Pinecone delete all exception: ' . $e->getMessage());
552 return array(
553 'success' => false,
554 'message' => $e->getMessage()
555 );
556 }
557 }
558
559
560 /**
561 * Deletes batch of vectors from Pinecone database
562 */
563 private function mxchat_delete_pinecone_batch($vector_ids, $api_key, $host) {
564 // Build the API endpoint
565 $api_endpoint = "https://{$host}/vectors/delete";
566
567 // Prepare the request body with the IDs
568 $request_body = array(
569 'ids' => $vector_ids
570 );
571
572 // Make the deletion request
573 $response = wp_remote_post($api_endpoint, array(
574 'headers' => array(
575 'Api-Key' => $api_key,
576 'accept' => 'application/json',
577 'content-type' => 'application/json'
578 ),
579 'body' => wp_json_encode($request_body),
580 'timeout' => 60, // Increased timeout for batch operations
581 'method' => 'POST'
582 ));
583
584 // Handle WordPress HTTP API errors
585 if (is_wp_error($response)) {
586 return array(
587 'success' => false,
588 'message' => $response->get_error_message()
589 );
590 }
591
592 // Check response status
593 $response_code = wp_remote_retrieve_response_code($response);
594 $response_body = wp_remote_retrieve_body($response);
595
596 // Pinecone returns 200 for successful deletion
597 if ($response_code !== 200) {
598 //error_log('Pinecone batch deletion failed: HTTP ' . $response_code . ' - ' . $response_body);
599 return array(
600 'success' => false,
601 'message' => sprintf(
602 'Pinecone API error (HTTP %d): %s',
603 $response_code,
604 $response_body
605 )
606 );
607 }
608
609 return array(
610 'success' => true,
611 'message' => 'Batch deleted successfully from Pinecone'
612 );
613 }
614
615
616 /**
617 * Deletes vector from Pinecone using API request
618 */
619 public function mxchat_delete_from_pinecone_by_vector_id($vector_id, $api_key, $host) {
620 // Build the API endpoint
621 $api_endpoint = "https://{$host}/vectors/delete";
622
623 // Prepare the request body with just the ID
624 $request_body = array(
625 'ids' => array($vector_id)
626 );
627
628 // Make the deletion request
629 $response = wp_remote_post($api_endpoint, array(
630 'headers' => array(
631 'Api-Key' => $api_key,
632 'accept' => 'application/json',
633 'content-type' => 'application/json'
634 ),
635 'body' => wp_json_encode($request_body),
636 'timeout' => 30,
637 'method' => 'POST'
638 ));
639
640 // Handle WordPress HTTP API errors
641 if (is_wp_error($response)) {
642 return array(
643 'success' => false,
644 'message' => $response->get_error_message()
645 );
646 }
647
648 // Check response status
649 $response_code = wp_remote_retrieve_response_code($response);
650 $response_body = wp_remote_retrieve_body($response);
651
652 // Pinecone returns 200 for successful deletion
653 if ($response_code !== 200) {
654 //error_log('Pinecone deletion failed: HTTP ' . $response_code . ' - ' . $response_body);
655 return array(
656 'success' => false,
657 'message' => sprintf(
658 'Pinecone API error (HTTP %d): %s',
659 $response_code,
660 $response_body
661 )
662 );
663 }
664
665 // Parse response to check if it was successful
666 $response_data = json_decode($response_body, true);
667
668 // Log successful deletion
669 //error_log('Pinecone vector ' . $vector_id . ' deleted successfully');
670
671 return array(
672 'success' => true,
673 'message' => 'Vector deleted successfully from Pinecone'
674 );
675 }
676
677
678 /**
679 * Deletes data from Pinecone using a source URL
680 */
681 public function mxchat_delete_from_pinecone_by_url($source_url, $pinecone_options) {
682 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
683 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
684
685 if (empty($host) || empty($api_key)) {
686 //error_log('MXChat: Pinecone deletion failed - missing configuration');
687 return false;
688 }
689
690 $api_endpoint = "https://{$host}/vectors/delete";
691 $vector_id = md5($source_url);
692
693 $request_body = array(
694 'ids' => array($vector_id)
695 );
696
697 $response = wp_remote_post($api_endpoint, array(
698 'headers' => array(
699 'Api-Key' => $api_key,
700 'accept' => 'application/json',
701 'content-type' => 'application/json'
702 ),
703 'body' => wp_json_encode($request_body),
704 'timeout' => 30
705 ));
706
707 if (is_wp_error($response)) {
708 //error_log('MXChat: Pinecone deletion error - ' . $response->get_error_message());
709 return false;
710 }
711
712 $response_code = wp_remote_retrieve_response_code($response);
713 if ($response_code !== 200) {
714 //error_log('MXChat: Pinecone deletion failed with status ' . $response_code);
715 return false;
716 }
717
718 return true;
719 }
720
721
722 /**
723 * Deletes data from Pinecone index using API key
724 */
725 private function mxchat_delete_from_pinecone($urls, $api_key, $environment, $index_name) {
726 // Get the Pinecone host from options (matching your store_in_pinecone_main pattern)
727 $options = get_option('mxchat_pinecone_addon_options');
728 $host = $options['mxchat_pinecone_host'] ?? '';
729
730 if (empty($host)) {
731 return array(
732 'success' => false,
733 'message' => 'Pinecone host is not configured. Please set the host in your settings.'
734 );
735 }
736
737 // Build API endpoint using the configured host
738 $api_endpoint = "https://{$host}/vectors/delete";
739
740 // Create vector IDs from URLs (matching your store method's ID generation)
741 $vector_ids = array_map('md5', $urls);
742
743 // Prepare the delete request body
744 $request_body = array(
745 'ids' => $vector_ids,
746 'filter' => array(
747 'source_url' => array(
748 '$in' => $urls
749 )
750 )
751 );
752
753 // Make the deletion request
754 $response = wp_remote_post($api_endpoint, array(
755 'headers' => array(
756 'Api-Key' => $api_key,
757 'accept' => 'application/json',
758 'content-type' => 'application/json'
759 ),
760 'body' => wp_json_encode($request_body),
761 'timeout' => 30,
762 'data_format' => 'body'
763 ));
764
765 // Handle WordPress HTTP API errors
766 if (is_wp_error($response)) {
767 return array(
768 'success' => false,
769 'message' => $response->get_error_message()
770 );
771 }
772
773 // Check response status
774 $response_code = wp_remote_retrieve_response_code($response);
775 if ($response_code !== 200) {
776 $body = wp_remote_retrieve_body($response);
777 return array(
778 'success' => false,
779 'message' => sprintf(
780 'Pinecone API error (HTTP %d): %s',
781 $response_code,
782 $body
783 )
784 );
785 }
786
787 // Parse response body
788 $body = wp_remote_retrieve_body($response);
789 $response_data = json_decode($body, true);
790
791 // Final validation of the response
792 if (json_last_error() !== JSON_ERROR_NONE) {
793 return array(
794 'success' => false,
795 'message' => 'Failed to parse Pinecone response: ' . json_last_error_msg()
796 );
797 }
798
799 return array(
800 'success' => true,
801 'message' => sprintf('Successfully deleted %d vectors from Pinecone', count($vector_ids))
802 );
803 }
804
805
806 // ========================================
807 // VECTOR CACHE MANAGEMENT
808 // ========================================
809
810
811 /**
812 * Removes vector ID from cache array option
813 */
814 public function mxchat_remove_from_pinecone_vector_cache($vector_id) {
815 $cached_ids = get_option('mxchat_pinecone_vector_ids_cache', array());
816 $key = array_search($vector_id, $cached_ids);
817 if ($key !== false) {
818 unset($cached_ids[$key]);
819 update_option('mxchat_pinecone_vector_ids_cache', array_values($cached_ids));
820 }
821 }
822
823 /**
824 * Removes vector ID from processed content caches
825 */
826 public function mxchat_remove_from_processed_content_caches($vector_id) {
827 // Get all caches
828 $pinecone_cache = get_option('mxchat_pinecone_processed_cache', array());
829 $processed_cache = get_option('mxchat_processed_content_cache', array());
830
831 // We need to find the post ID that corresponds to this vector ID
832 // Vector ID is typically md5 of the source URL
833 $post_id_to_remove = null;
834
835 // Search through caches to find matching post
836 foreach ($pinecone_cache as $post_id => $cache_data) {
837 if (isset($cache_data['db_id']) && $cache_data['db_id'] === $vector_id) {
838 $post_id_to_remove = $post_id;
839 break;
840 }
841 }
842
843 // Also check the processed cache
844 if (!$post_id_to_remove) {
845 foreach ($processed_cache as $post_id => $cache_data) {
846 if (isset($cache_data['db_id']) && $cache_data['db_id'] === $vector_id) {
847 $post_id_to_remove = $post_id;
848 break;
849 }
850 }
851 }
852
853 // If we found the post ID, remove it from both caches
854 if ($post_id_to_remove) {
855 unset($pinecone_cache[$post_id_to_remove]);
856 unset($processed_cache[$post_id_to_remove]);
857
858 update_option('mxchat_pinecone_processed_cache', $pinecone_cache);
859 update_option('mxchat_processed_content_cache', $processed_cache);
860
861 //error_log('Removed post ID ' . $post_id_to_remove . ' from processed content caches');
862 } else {
863 // If we can't find by vector ID, we might need to reconstruct the URL
864 // and find the post ID that way
865 //error_log('Could not find post ID for vector ID: ' . $vector_id);
866 }
867 }
868
869
870 /**
871 * Retrieves and caches Pinecone API processed content
872 */
873 public function mxchat_get_pinecone_processed_content($pinecone_options) {
874 // First check local cache for immediate updates
875 $cached_data = get_option('mxchat_pinecone_processed_cache', array());
876
877 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
878 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
879
880 if (empty($api_key) || empty($host)) {
881 // Return only cached data if API credentials are missing
882 return $cached_data;
883 }
884
885 $pinecone_data = array();
886
887 try {
888 // Method 1: Try to get vectors using cached vector IDs first
889 $cached_vector_ids = get_option('mxchat_pinecone_vector_ids_cache', array());
890
891 if (!empty($cached_vector_ids)) {
892 $pinecone_data = $this->fetch_pinecone_vectors_by_ids($pinecone_options, $cached_vector_ids);
893 }
894
895 // Method 2: If no cached IDs or fetch failed, use scanning approach
896 if (empty($pinecone_data)) {
897 $pinecone_data = $this->mxchat_scan_pinecone_for_processed_content($pinecone_options);
898 }
899
900 // Method 3: Final fallback - try stats endpoint (if available)
901 if (empty($pinecone_data)) {
902 $stats_url = "https://{$host}/describe_index_stats";
903
904 $response = wp_remote_post($stats_url, array(
905 'headers' => array(
906 'Api-Key' => $api_key,
907 'Content-Type' => 'application/json'
908 ),
909 'body' => json_encode(array()),
910 'timeout' => 30
911 ));
912
913 if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
914 $body = wp_remote_retrieve_body($response);
915 $stats_data = json_decode($body, true);
916
917 // Log stats for debugging but don't rely on them for vector listing
918 //error_log('Pinecone index stats: ' . print_r($stats_data, true));
919 }
920 }
921
922 } catch (Exception $e) {
923 //error_log('Pinecone processed content exception: ' . $e->getMessage());
924 }
925
926 // Merge cached data with Pinecone data
927 // Cache takes priority for recent updates (within last 5 minutes)
928 $merged_data = $pinecone_data;
929
930 foreach ($cached_data as $post_id => $cache_item) {
931 $cache_timestamp = $cache_item['timestamp'] ?? 0;
932 $time_diff = current_time('timestamp') - $cache_timestamp;
933
934 // If cache item is recent (less than 5 minutes), prioritize it
935 if ($time_diff < 300) { // 5 minutes = 300 seconds
936 $merged_data[$post_id] = $cache_item;
937 } else {
938 // If not in Pinecone data and cache is old, keep cache but mark as potentially stale
939 if (!isset($merged_data[$post_id])) {
940 $merged_data[$post_id] = $cache_item;
941 }
942 }
943 }
944
945 return $merged_data;
946 }
947
948
949 // ========================================
950 // HELPER METHODS
951 // ========================================
952
953 /**
954 * Validates Pinecone API credentials
955 */
956 private function mxchat_validate_pinecone_credentials($api_key, $host) {
957 if (empty($api_key) || empty($host)) {
958 return false;
959 }
960 return true;
961 }
962
963 /**
964 * Get Pinecone API credentials from options
965 */
966 private function mxchat_get_pinecone_credentials() {
967 $options = get_option('mxchat_options', array());
968 return array(
969 'api_key' => isset($options['pinecone_api_key']) ? $options['pinecone_api_key'] : '',
970 'host' => isset($options['pinecone_host']) ? $options['pinecone_host'] : ''
971 );
972 }
973
974 /**
975 * Log Pinecone operation errors
976 */
977 private function log_pinecone_error($operation, $error_message) {
978 //error_log("MxChat Pinecone {$operation} Error: " . $error_message);
979 }
980
981 // ========================================
982 // STATIC ACCESS METHODS (for backward compatibility)
983 // ========================================
984
985 /**
986 * Get singleton instance
987 */
988 public static function get_instance() {
989 static $instance = null;
990 if ($instance === null) {
991 $instance = new self();
992 }
993 return $instance;
994 }
995 }
996
997 // Initialize the Pinecone manager
998 $mxchat_pinecone_manager = MxChat_Pinecone_Manager::get_instance();
999