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

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