PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.2.4
MxChat – AI Chatbot & Content Generation for WordPress v2.2.4
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.4, at admin/class-pinecone-manager.php

866 lines 31.3 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 /**
28 * Fetches 1K most recent records from Pinecone (SIMPLIFIED VERSION)
29 */
30 public function mxchat_fetch_pinecone_records($pinecone_options, $search_query = '', $page = 1, $per_page = 20) {
31 //error_log('=== DEBUG: mxchat_fetch_pinecone_records started (simplified 1K) ===');
32
33 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
34 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
35
36 if (empty($api_key) || empty($host)) {
37 //error_log('DEBUG: Missing required Pinecone parameters');
38 return array('data' => array(), 'total' => 0, 'total_in_database' => 0, 'showing_recent_only' => false);
39 }
40
41 try {
42 // Get total count for the banner message
43 $total_in_database = $this->mxchat_get_pinecone_total_count($pinecone_options);
44
45 // Get 1K most recent entries
46 $all_records = $this->mxchat_get_recent_1k_entries($pinecone_options);
47
48 // Filter by search query if provided
49 if (!empty($search_query)) {
50 $all_records = array_filter($all_records, function($record) use ($search_query) {
51 $content = $record->article_content ?? '';
52 $source_url = $record->source_url ?? '';
53 return stripos($content, $search_query) !== false || stripos($source_url, $search_query) !== false;
54 });
55 }
56
57 // Handle pagination
58 $total = count($all_records);
59 $offset = ($page - 1) * $per_page;
60 $paged_records = array_slice($all_records, $offset, $per_page);
61
62 //error_log('DEBUG: Returning ' . count($paged_records) . ' records (page ' . $page . ' of ' . ceil($total / $per_page) . ')');
63
64 return array(
65 'data' => $paged_records,
66 'total' => $total,
67 'total_in_database' => $total_in_database,
68 'showing_recent_only' => ($total_in_database > 1000)
69 );
70
71 } catch (Exception $e) {
72 //error_log('DEBUG: Exception: ' . $e->getMessage());
73 return array('data' => array(), 'total' => 0, 'total_in_database' => 0, 'showing_recent_only' => false);
74 }
75 }
76
77 /**
78 * Get 1,000 most recent entries from Pinecone (SIMPLE VERSION)
79 */
80 private function mxchat_get_recent_1k_entries($pinecone_options) {
81 //error_log('=== DEBUG: mxchat_get_recent_1k_entries started ===');
82
83 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
84 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
85
86 if (empty($api_key) || empty($host)) {
87 return array();
88 }
89
90 try {
91 // Fetch multiple batches to get a good sample, then sort by date
92 $all_records = array();
93 $seen_ids = array();
94 $query_url = "https://{$host}/query";
95
96 // Do 5 passes to get a good sample of records
97 for ($pass = 0; $pass < 5; $pass++) {
98 //error_log('DEBUG: Fetching batch ' . ($pass + 1) . '/5');
99
100 // Generate random vector for similarity search
101 $random_vector = array();
102 for ($i = 0; $i < 1536; $i++) {
103 $random_vector[] = (rand(-1000, 1000) / 1000.0);
104 }
105
106 // Normalize vector
107 $magnitude = sqrt(array_sum(array_map(function($x) { return $x * $x; }, $random_vector)));
108 if ($magnitude > 0) {
109 $random_vector = array_map(function($x) use ($magnitude) { return $x / $magnitude; }, $random_vector);
110 }
111
112 $query_data = array(
113 'includeMetadata' => true,
114 'includeValues' => false,
115 'topK' => 2000, // Get 2K per batch
116 'vector' => $random_vector
117 );
118
119 $response = wp_remote_post($query_url, array(
120 'headers' => array(
121 'Api-Key' => $api_key,
122 'Content-Type' => 'application/json'
123 ),
124 'body' => json_encode($query_data),
125 'timeout' => 30
126 ));
127
128 if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
129 $body = wp_remote_retrieve_body($response);
130 $data = json_decode($body, true);
131
132 if (isset($data['matches'])) {
133 foreach ($data['matches'] as $match) {
134 $match_id = $match['id'] ?? '';
135 if (!empty($match_id) && !isset($seen_ids[$match_id])) {
136 $metadata = $match['metadata'] ?? array();
137 $created_at = $metadata['created_at'] ?? $metadata['last_updated'] ?? time();
138
139 // Ensure valid timestamp
140 if (!is_numeric($created_at)) {
141 $created_at = strtotime($created_at) ?: time();
142 }
143
144 $all_records[] = (object) array(
145 'id' => $match_id,
146 'article_content' => $metadata['text'] ?? '',
147 'source_url' => $metadata['source_url'] ?? '',
148 'created_at' => $created_at,
149 'data_source' => 'pinecone'
150 );
151
152 $seen_ids[$match_id] = true;
153 }
154 }
155 }
156 }
157
158 usleep(100000); // 0.1 second delay between batches
159 }
160
161 // Sort by created_at (newest first) and take top 1K
162 usort($all_records, function($a, $b) {
163 return $b->created_at - $a->created_at;
164 });
165
166 $recent_1k = array_slice($all_records, 0, 1000);
167
168 //error_log('DEBUG: Found ' . count($all_records) . ' total records, returning top ' . count($recent_1k));
169
170 return $recent_1k;
171
172 } catch (Exception $e) {
173 //error_log('DEBUG: Exception in get_recent_1k_entries: ' . $e->getMessage());
174 return array();
175 }
176 }
177
178 /**
179 * Get total count of vectors in Pinecone (SIMPLE VERSION)
180 */
181 private function mxchat_get_pinecone_total_count($pinecone_options) {
182 // First try the stats API
183 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
184 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
185
186 if (empty($api_key) || empty($host)) {
187 return 0;
188 }
189
190 try {
191 $stats_url = "https://{$host}/describe_index_stats";
192
193 // Try GET request
194 $response = wp_remote_get($stats_url, array(
195 'headers' => array(
196 'Api-Key' => $api_key,
197 'Accept' => 'application/json'
198 ),
199 'timeout' => 15
200 ));
201
202 if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
203 $body = wp_remote_retrieve_body($response);
204 $stats_data = json_decode($body, true);
205
206 $total_count = $stats_data['totalVectorCount'] ?? 0;
207 if ($total_count > 0) {
208 //error_log('DEBUG: Got total count from stats API: ' . $total_count);
209 return intval($total_count);
210 }
211 }
212
213 // Fallback: estimate from previous scans
214 $cached_vector_ids = get_option('mxchat_pinecone_vector_ids_cache', array());
215 if (!empty($cached_vector_ids)) {
216 $estimated_count = count($cached_vector_ids);
217 //error_log('DEBUG: Using estimated count from cache: ' . $estimated_count);
218 return intval($estimated_count);
219 }
220
221 } catch (Exception $e) {
222 //error_log('DEBUG: Exception getting total count: ' . $e->getMessage());
223 }
224
225 // If all else fails, return 0
226 return 0;
227 }
228
229 /**
230 * Call this after adding new content to refresh the view
231 */
232 public function mxchat_refresh_after_new_content($pinecone_options) {
233 //error_log('DEBUG: Refreshing after new content added');
234
235 // Clear any old caches
236 delete_transient('mxchat_pinecone_recent_1k');
237 delete_transient('mxchat_pinecone_total_count');
238
239 // Force fresh fetch on next page load
240 // The next call to mxchat_fetch_pinecone_records will get fresh data
241
242 return true;
243 }
244
245
246 /**
247 * Fetches vectors from Pinecone using provided IDs (for content selection feature)
248 */
249 public function fetch_pinecone_vectors_by_ids($pinecone_options, $vector_ids) {
250 //error_log('=== DEBUG: fetch_pinecone_vectors_by_ids started (content selection method) ===');
251
252 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
253 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
254
255 //error_log('DEBUG: API key present: ' . (!empty($api_key) ? 'YES' : 'NO'));
256 //error_log('DEBUG: Host: ' . $host);
257 //error_log('DEBUG: Vector IDs count: ' . count($vector_ids));
258
259 if (empty($api_key) || empty($host) || empty($vector_ids)) {
260 //error_log('DEBUG: Missing parameters for fetch by IDs (content selection)');
261 return array();
262 }
263
264 try {
265 $fetch_url = "https://{$host}/vectors/fetch";
266 //error_log('DEBUG: Fetch URL: ' . $fetch_url);
267
268 // Pinecone fetch API allows fetching specific vectors by ID
269 $fetch_data = array(
270 'ids' => array_values($vector_ids)
271 );
272
273 $response = wp_remote_post($fetch_url, array(
274 'headers' => array(
275 'Api-Key' => $api_key,
276 'Content-Type' => 'application/json'
277 ),
278 'body' => json_encode($fetch_data),
279 'timeout' => 30
280 ));
281
282 if (is_wp_error($response)) {
283 //error_log('DEBUG: Fetch by IDs WP error (content selection): ' . $response->get_error_message());
284 return array();
285 }
286
287 $response_code = wp_remote_retrieve_response_code($response);
288 //error_log('DEBUG: Fetch response code (content selection): ' . $response_code);
289
290 if ($response_code !== 200) {
291 $error_body = wp_remote_retrieve_body($response);
292 //error_log('DEBUG: Fetch failed with body (content selection): ' . $error_body);
293 return array();
294 }
295
296 $body = wp_remote_retrieve_body($response);
297 $data = json_decode($body, true);
298
299 if (!isset($data['vectors'])) {
300 //error_log('DEBUG: No vectors key in response (content selection)');
301 return array();
302 }
303
304 $processed_data = array();
305
306 foreach ($data['vectors'] as $vector_id => $vector_data) {
307 $metadata = $vector_data['metadata'] ?? array();
308 $source_url = $metadata['source_url'] ?? '';
309
310 if (!empty($source_url)) {
311 $post_id = url_to_postid($source_url);
312 if ($post_id) {
313 $created_at = $metadata['created_at'] ?? '';
314 $processed_date = 'Recently'; // Default
315
316 if (!empty($created_at)) {
317 $timestamp = is_numeric($created_at) ? $created_at : strtotime($created_at);
318 if ($timestamp) {
319 $processed_date = human_time_diff($timestamp, current_time('timestamp')) . ' ago';
320 }
321 }
322
323 $processed_data[$post_id] = array(
324 'db_id' => $vector_id,
325 'processed_date' => $processed_date,
326 'url' => $source_url,
327 'source' => 'pinecone',
328 'timestamp' => $timestamp ?? current_time('timestamp')
329 );
330 }
331 }
332 }
333
334 //error_log('DEBUG: Processed ' . count($processed_data) . ' records (content selection method)');
335 //error_log('=== DEBUG: fetch_pinecone_vectors_by_ids completed (content selection) ===');
336
337 return $processed_data;
338
339 } catch (Exception $e) {
340 //error_log('DEBUG: Exception in fetch_pinecone_vectors_by_ids (content selection): ' . $e->getMessage());
341 return array();
342 }
343 }
344
345
346 // ========================================
347 // PINECONE DELETE OPERATIONS
348 // ========================================
349
350 /**
351 * Deletes data from Pinecone using provided API credentials
352 */
353 public function mxchat_delete_all_from_pinecone($pinecone_options) {
354 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
355 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
356
357 if (empty($api_key) || empty($host)) {
358 return array(
359 'success' => false,
360 'message' => 'Missing Pinecone API credentials'
361 );
362 }
363
364 try {
365 // First, get all vector IDs
366 $all_vector_ids = array();
367
368 // Try to get from cache first
369 $cached_vector_ids = get_option('mxchat_pinecone_vector_ids_cache', array());
370 if (!empty($cached_vector_ids)) {
371 $all_vector_ids = $cached_vector_ids;
372 } else {
373 // Fallback: scan to get vector IDs
374 $records = $this->mxchat_scan_pinecone_vectors($pinecone_options);
375 foreach ($records as $record) {
376 if (!empty($record->id)) {
377 $all_vector_ids[] = $record->id;
378 }
379 }
380 }
381
382 if (empty($all_vector_ids)) {
383 return array(
384 'success' => true,
385 'message' => 'No vectors found to delete'
386 );
387 }
388
389 // Delete vectors in batches (Pinecone has limits on batch operations)
390 $batch_size = 100;
391 $batches = array_chunk($all_vector_ids, $batch_size);
392 $deleted_count = 0;
393 $failed_batches = 0;
394
395 foreach ($batches as $batch) {
396 $result = $this->mxchat_delete_pinecone_batch($batch, $api_key, $host);
397 if ($result['success']) {
398 $deleted_count += count($batch);
399 } else {
400 $failed_batches++;
401 //error_log('Failed to delete Pinecone batch: ' . $result['message']);
402 }
403 }
404
405 if ($failed_batches > 0) {
406 return array(
407 'success' => false,
408 'message' => sprintf('Deleted %d vectors, but %d batches failed', $deleted_count, $failed_batches)
409 );
410 }
411
412 return array(
413 'success' => true,
414 'message' => "Successfully deleted {$deleted_count} vectors from Pinecone"
415 );
416
417 } catch (Exception $e) {
418 //error_log('Pinecone delete all exception: ' . $e->getMessage());
419 return array(
420 'success' => false,
421 'message' => $e->getMessage()
422 );
423 }
424 }
425
426
427 /**
428 * Deletes batch of vectors from Pinecone database
429 */
430 private function mxchat_delete_pinecone_batch($vector_ids, $api_key, $host) {
431 // Build the API endpoint
432 $api_endpoint = "https://{$host}/vectors/delete";
433
434 // Prepare the request body with the IDs
435 $request_body = array(
436 'ids' => $vector_ids
437 );
438
439 // Make the deletion request
440 $response = wp_remote_post($api_endpoint, array(
441 'headers' => array(
442 'Api-Key' => $api_key,
443 'accept' => 'application/json',
444 'content-type' => 'application/json'
445 ),
446 'body' => wp_json_encode($request_body),
447 'timeout' => 60, // Increased timeout for batch operations
448 'method' => 'POST'
449 ));
450
451 // Handle WordPress HTTP API errors
452 if (is_wp_error($response)) {
453 return array(
454 'success' => false,
455 'message' => $response->get_error_message()
456 );
457 }
458
459 // Check response status
460 $response_code = wp_remote_retrieve_response_code($response);
461 $response_body = wp_remote_retrieve_body($response);
462
463 // Pinecone returns 200 for successful deletion
464 if ($response_code !== 200) {
465 //error_log('Pinecone batch deletion failed: HTTP ' . $response_code . ' - ' . $response_body);
466 return array(
467 'success' => false,
468 'message' => sprintf(
469 'Pinecone API error (HTTP %d): %s',
470 $response_code,
471 $response_body
472 )
473 );
474 }
475
476 return array(
477 'success' => true,
478 'message' => 'Batch deleted successfully from Pinecone'
479 );
480 }
481
482
483 /**
484 * Deletes vector from Pinecone using API request
485 */
486 public function mxchat_delete_from_pinecone_by_vector_id($vector_id, $api_key, $host) {
487 // Build the API endpoint
488 $api_endpoint = "https://{$host}/vectors/delete";
489
490 // Prepare the request body with just the ID
491 $request_body = array(
492 'ids' => array($vector_id)
493 );
494
495 // Make the deletion request
496 $response = wp_remote_post($api_endpoint, array(
497 'headers' => array(
498 'Api-Key' => $api_key,
499 'accept' => 'application/json',
500 'content-type' => 'application/json'
501 ),
502 'body' => wp_json_encode($request_body),
503 'timeout' => 30,
504 'method' => 'POST'
505 ));
506
507 // Handle WordPress HTTP API errors
508 if (is_wp_error($response)) {
509 return array(
510 'success' => false,
511 'message' => $response->get_error_message()
512 );
513 }
514
515 // Check response status
516 $response_code = wp_remote_retrieve_response_code($response);
517 $response_body = wp_remote_retrieve_body($response);
518
519 // Pinecone returns 200 for successful deletion
520 if ($response_code !== 200) {
521 //error_log('Pinecone deletion failed: HTTP ' . $response_code . ' - ' . $response_body);
522 return array(
523 'success' => false,
524 'message' => sprintf(
525 'Pinecone API error (HTTP %d): %s',
526 $response_code,
527 $response_body
528 )
529 );
530 }
531
532 // Parse response to check if it was successful
533 $response_data = json_decode($response_body, true);
534
535 // Log successful deletion
536 //error_log('Pinecone vector ' . $vector_id . ' deleted successfully');
537
538 return array(
539 'success' => true,
540 'message' => 'Vector deleted successfully from Pinecone'
541 );
542 }
543
544
545 /**
546 * Deletes data from Pinecone using a source URL
547 */
548 public function mxchat_delete_from_pinecone_by_url($source_url, $pinecone_options) {
549 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
550 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
551
552 if (empty($host) || empty($api_key)) {
553 //error_log('MXChat: Pinecone deletion failed - missing configuration');
554 return false;
555 }
556
557 $api_endpoint = "https://{$host}/vectors/delete";
558 $vector_id = md5($source_url);
559
560 $request_body = array(
561 'ids' => array($vector_id)
562 );
563
564 $response = wp_remote_post($api_endpoint, array(
565 'headers' => array(
566 'Api-Key' => $api_key,
567 'accept' => 'application/json',
568 'content-type' => 'application/json'
569 ),
570 'body' => wp_json_encode($request_body),
571 'timeout' => 30
572 ));
573
574 if (is_wp_error($response)) {
575 //error_log('MXChat: Pinecone deletion error - ' . $response->get_error_message());
576 return false;
577 }
578
579 $response_code = wp_remote_retrieve_response_code($response);
580 if ($response_code !== 200) {
581 //error_log('MXChat: Pinecone deletion failed with status ' . $response_code);
582 return false;
583 }
584
585 return true;
586 }
587
588
589 /**
590 * Deletes data from Pinecone index using API key
591 */
592 private function mxchat_delete_from_pinecone($urls, $api_key, $environment, $index_name) {
593 // Get the Pinecone host from options (matching your store_in_pinecone_main pattern)
594 $options = get_option('mxchat_pinecone_addon_options');
595 $host = $options['mxchat_pinecone_host'] ?? '';
596
597 if (empty($host)) {
598 return array(
599 'success' => false,
600 'message' => 'Pinecone host is not configured. Please set the host in your settings.'
601 );
602 }
603
604 // Build API endpoint using the configured host
605 $api_endpoint = "https://{$host}/vectors/delete";
606
607 // Create vector IDs from URLs (matching your store method's ID generation)
608 $vector_ids = array_map('md5', $urls);
609
610 // Prepare the delete request body
611 $request_body = array(
612 'ids' => $vector_ids,
613 'filter' => array(
614 'source_url' => array(
615 '$in' => $urls
616 )
617 )
618 );
619
620 // Make the deletion request
621 $response = wp_remote_post($api_endpoint, array(
622 'headers' => array(
623 'Api-Key' => $api_key,
624 'accept' => 'application/json',
625 'content-type' => 'application/json'
626 ),
627 'body' => wp_json_encode($request_body),
628 'timeout' => 30,
629 'data_format' => 'body'
630 ));
631
632 // Handle WordPress HTTP API errors
633 if (is_wp_error($response)) {
634 return array(
635 'success' => false,
636 'message' => $response->get_error_message()
637 );
638 }
639
640 // Check response status
641 $response_code = wp_remote_retrieve_response_code($response);
642 if ($response_code !== 200) {
643 $body = wp_remote_retrieve_body($response);
644 return array(
645 'success' => false,
646 'message' => sprintf(
647 'Pinecone API error (HTTP %d): %s',
648 $response_code,
649 $body
650 )
651 );
652 }
653
654 // Parse response body
655 $body = wp_remote_retrieve_body($response);
656 $response_data = json_decode($body, true);
657
658 // Final validation of the response
659 if (json_last_error() !== JSON_ERROR_NONE) {
660 return array(
661 'success' => false,
662 'message' => 'Failed to parse Pinecone response: ' . json_last_error_msg()
663 );
664 }
665
666 return array(
667 'success' => true,
668 'message' => sprintf('Successfully deleted %d vectors from Pinecone', count($vector_ids))
669 );
670 }
671
672
673 // ========================================
674 // VECTOR CACHE MANAGEMENT
675 // ========================================
676
677
678 /**
679 * Removes vector ID from cache array option
680 */
681 public function mxchat_remove_from_pinecone_vector_cache($vector_id) {
682 $cached_ids = get_option('mxchat_pinecone_vector_ids_cache', array());
683 $key = array_search($vector_id, $cached_ids);
684 if ($key !== false) {
685 unset($cached_ids[$key]);
686 update_option('mxchat_pinecone_vector_ids_cache', array_values($cached_ids));
687 }
688 }
689
690 /**
691 * Removes vector ID from processed content caches
692 */
693 public function mxchat_remove_from_processed_content_caches($vector_id) {
694 // Get all caches
695 $pinecone_cache = get_option('mxchat_pinecone_processed_cache', array());
696 $processed_cache = get_option('mxchat_processed_content_cache', array());
697
698 // We need to find the post ID that corresponds to this vector ID
699 // Vector ID is typically md5 of the source URL
700 $post_id_to_remove = null;
701
702 // Search through caches to find matching post
703 foreach ($pinecone_cache as $post_id => $cache_data) {
704 if (isset($cache_data['db_id']) && $cache_data['db_id'] === $vector_id) {
705 $post_id_to_remove = $post_id;
706 break;
707 }
708 }
709
710 // Also check the processed cache
711 if (!$post_id_to_remove) {
712 foreach ($processed_cache as $post_id => $cache_data) {
713 if (isset($cache_data['db_id']) && $cache_data['db_id'] === $vector_id) {
714 $post_id_to_remove = $post_id;
715 break;
716 }
717 }
718 }
719
720 // If we found the post ID, remove it from both caches
721 if ($post_id_to_remove) {
722 unset($pinecone_cache[$post_id_to_remove]);
723 unset($processed_cache[$post_id_to_remove]);
724
725 update_option('mxchat_pinecone_processed_cache', $pinecone_cache);
726 update_option('mxchat_processed_content_cache', $processed_cache);
727
728 //error_log('Removed post ID ' . $post_id_to_remove . ' from processed content caches');
729 } else {
730 // If we can't find by vector ID, we might need to reconstruct the URL
731 // and find the post ID that way
732 //error_log('Could not find post ID for vector ID: ' . $vector_id);
733 }
734 }
735
736
737 /**
738 * Retrieves and caches Pinecone API processed content
739 */
740 public function mxchat_get_pinecone_processed_content($pinecone_options) {
741 // First check local cache for immediate updates
742 $cached_data = get_option('mxchat_pinecone_processed_cache', array());
743
744 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
745 $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
746
747 if (empty($api_key) || empty($host)) {
748 // Return only cached data if API credentials are missing
749 return $cached_data;
750 }
751
752 $pinecone_data = array();
753
754 try {
755 // Method 1: Try to get vectors using cached vector IDs first
756 $cached_vector_ids = get_option('mxchat_pinecone_vector_ids_cache', array());
757
758 if (!empty($cached_vector_ids)) {
759 $pinecone_data = $this->fetch_pinecone_vectors_by_ids($pinecone_options, $cached_vector_ids);
760 }
761
762 // Method 2: If no cached IDs or fetch failed, use scanning approach
763 if (empty($pinecone_data)) {
764 $pinecone_data = $this->mxchat_scan_pinecone_for_processed_content($pinecone_options);
765 }
766
767 // Method 3: Final fallback - try stats endpoint (if available)
768 if (empty($pinecone_data)) {
769 $stats_url = "https://{$host}/describe_index_stats";
770
771 $response = wp_remote_post($stats_url, array(
772 'headers' => array(
773 'Api-Key' => $api_key,
774 'Content-Type' => 'application/json'
775 ),
776 'body' => json_encode(array()),
777 'timeout' => 30
778 ));
779
780 if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
781 $body = wp_remote_retrieve_body($response);
782 $stats_data = json_decode($body, true);
783
784 // Log stats for debugging but don't rely on them for vector listing
785 //error_log('Pinecone index stats: ' . print_r($stats_data, true));
786 }
787 }
788
789 } catch (Exception $e) {
790 //error_log('Pinecone processed content exception: ' . $e->getMessage());
791 }
792
793 // Merge cached data with Pinecone data
794 // Cache takes priority for recent updates (within last 5 minutes)
795 $merged_data = $pinecone_data;
796
797 foreach ($cached_data as $post_id => $cache_item) {
798 $cache_timestamp = $cache_item['timestamp'] ?? 0;
799 $time_diff = current_time('timestamp') - $cache_timestamp;
800
801 // If cache item is recent (less than 5 minutes), prioritize it
802 if ($time_diff < 300) { // 5 minutes = 300 seconds
803 $merged_data[$post_id] = $cache_item;
804 } else {
805 // If not in Pinecone data and cache is old, keep cache but mark as potentially stale
806 if (!isset($merged_data[$post_id])) {
807 $merged_data[$post_id] = $cache_item;
808 }
809 }
810 }
811
812 return $merged_data;
813 }
814
815
816 // ========================================
817 // HELPER METHODS
818 // ========================================
819
820 /**
821 * Validates Pinecone API credentials
822 */
823 private function mxchat_validate_pinecone_credentials($api_key, $host) {
824 if (empty($api_key) || empty($host)) {
825 return false;
826 }
827 return true;
828 }
829
830 /**
831 * Get Pinecone API credentials from options
832 */
833 private function mxchat_get_pinecone_credentials() {
834 $options = get_option('mxchat_options', array());
835 return array(
836 'api_key' => isset($options['pinecone_api_key']) ? $options['pinecone_api_key'] : '',
837 'host' => isset($options['pinecone_host']) ? $options['pinecone_host'] : ''
838 );
839 }
840
841 /**
842 * Log Pinecone operation errors
843 */
844 private function log_pinecone_error($operation, $error_message) {
845 //error_log("MxChat Pinecone {$operation} Error: " . $error_message);
846 }
847
848 // ========================================
849 // STATIC ACCESS METHODS (for backward compatibility)
850 // ========================================
851
852 /**
853 * Get singleton instance
854 */
855 public static function get_instance() {
856 static $instance = null;
857 if ($instance === null) {
858 $instance = new self();
859 }
860 return $instance;
861 }
862 }
863
864 // Initialize the Pinecone manager
865 $mxchat_pinecone_manager = MxChat_Pinecone_Manager::get_instance();
866