File: /var/www/html/www.winghung.com/wp-content/plugins/mxchat-basic/admin/class-pinecone-manager.php
<?php
/**
* File: admin/class-pinecone-manager.php
*
* Handles all Pinecone vector database operations for MxChat
*/
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
class MxChat_Pinecone_Manager {
/**
* Constructor
*/
public function __construct() {
// Hook into WordPress actions if needed
add_action('mxchat_delete_content', array($this, 'mxchat_delete_from_pinecone_by_url'), 10, 1);
}
// ========================================
// PINECONE FETCH OPERATIONS
// ========================================
/**
* Fetches 1K most recent records from Pinecone with bot-specific filtering
*/
public function mxchat_fetch_pinecone_records($pinecone_options, $search_query = '', $page = 1, $per_page = 20, $bot_id = 'default') {
//error_log('=== DEBUG: mxchat_fetch_pinecone_records ===');
//error_log('Bot ID: ' . $bot_id);
//error_log('Pinecone Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
//error_log('Pinecone Namespace: ' . ($pinecone_options['mxchat_pinecone_namespace'] ?? 'NOT SET'));
//error_log('Use Pinecone: ' . ($pinecone_options['mxchat_use_pinecone'] ?? 'NOT SET'));
$api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
$host = $pinecone_options['mxchat_pinecone_host'] ?? '';
$namespace = $pinecone_options['mxchat_pinecone_namespace'] ?? '';
if (empty($api_key) || empty($host)) {
return array('data' => array(), 'total' => 0, 'total_in_database' => 0, 'showing_recent_only' => false);
}
try {
// Get total count for the banner message (bot-specific)
$total_in_database = $this->mxchat_get_pinecone_total_count($pinecone_options, $bot_id);
// Always get fresh data - no caching
$all_records = $this->mxchat_get_recent_1k_entries($pinecone_options, $bot_id);
// Filter by search query if provided
if (!empty($search_query)) {
$all_records = array_filter($all_records, function($record) use ($search_query) {
$content = $record->article_content ?? '';
$source_url = $record->source_url ?? '';
return stripos($content, $search_query) !== false || stripos($source_url, $search_query) !== false;
});
}
// Handle pagination
$total = count($all_records);
$offset = ($page - 1) * $per_page;
$paged_records = array_slice($all_records, $offset, $per_page);
return array(
'data' => $paged_records,
'total' => $total,
'total_in_database' => $total_in_database,
'showing_recent_only' => ($total_in_database > 1000)
);
} catch (Exception $e) {
return array('data' => array(), 'total' => 0, 'total_in_database' => 0, 'showing_recent_only' => false);
}
}
/**
* Get embedding dimensions based on the selected model
* ADD THIS NEW FUNCTION
*/
private function mxchat_get_embedding_dimensions() {
$options = get_option('mxchat_options', array());
$selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
// Define dimensions for different models
$model_dimensions = array(
'text-embedding-ada-002' => 1536,
'text-embedding-3-small' => 1536,
'text-embedding-3-large' => 3072,
'voyage-2' => 1024,
'voyage-large-2' => 1536,
'voyage-3-large' => 2048,
'gemini-embedding-001' => 1536,
);
// Check if it's a voyage model with custom dimensions
if (strpos($selected_model, 'voyage-3-large') === 0) {
$custom_dimensions = $options['voyage_output_dimension'] ?? 2048;
return intval($custom_dimensions);
}
// Check if it's a gemini model with custom dimensions
if (strpos($selected_model, 'gemini-embedding') === 0) {
$custom_dimensions = $options['gemini_output_dimension'] ?? 1536;
return intval($custom_dimensions);
}
// Return known dimensions or default to 1536
return $model_dimensions[$selected_model] ?? 1536;
}
/**
* Generate random unit vector with correct dimensions
* ADD THIS NEW FUNCTION
*/
private function mxchat_generate_random_vector() {
$dimensions = $this->mxchat_get_embedding_dimensions();
$random_vector = array();
for ($i = 0; $i < $dimensions; $i++) {
$random_vector[] = (rand(-1000, 1000) / 1000.0);
}
// Normalize the vector to unit length
$magnitude = sqrt(array_sum(array_map(function($x) { return $x * $x; }, $random_vector)));
if ($magnitude > 0) {
$random_vector = array_map(function($x) use ($magnitude) { return $x / $magnitude; }, $random_vector);
}
return $random_vector;
}
/**
* Get recent 1K entries from Pinecone
*/
private function mxchat_get_recent_1k_entries($pinecone_options, $bot_id = 'default') {
global $wpdb;
//error_log('=== DEBUG: mxchat_get_recent_1k_entries started ===');
//error_log('DEBUG: Bot ID: ' . $bot_id);
$api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
$host = $pinecone_options['mxchat_pinecone_host'] ?? '';
$namespace = $pinecone_options['mxchat_pinecone_namespace'] ?? '';
if (empty($api_key) || empty($host)) {
return array();
}
try {
$all_records = array();
$seen_ids = array();
$query_url = "https://{$host}/query";
$fixed_vectors = $this->mxchat_generate_fixed_query_vectors();
foreach ($fixed_vectors as $vector_index => $query_vector) {
//error_log('DEBUG: Using fixed query vector ' . ($vector_index + 1) . '/' . count($fixed_vectors));
$query_data = array(
'includeMetadata' => true,
'includeValues' => false,
'topK' => 3000,
'vector' => $query_vector
);
// Add namespace if provided
if (!empty($namespace)) {
$query_data['namespace'] = $namespace;
}
$response = wp_remote_post($query_url, array(
'headers' => array(
'Api-Key' => $api_key,
'Content-Type' => 'application/json'
),
'body' => json_encode($query_data),
'timeout' => 30
));
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if (isset($data['matches'])) {
foreach ($data['matches'] as $match) {
$match_id = $match['id'] ?? '';
if (!empty($match_id) && !isset($seen_ids[$match_id])) {
$metadata = $match['metadata'] ?? array();
// Get created_at timestamp
$created_at = $metadata['created_at'] ??
$metadata['last_updated'] ??
$metadata['timestamp'] ??
time();
if (!is_numeric($created_at)) {
$created_at = strtotime($created_at) ?: time();
}
$all_records[] = (object) array(
'id' => $match_id,
'article_content' => $metadata['text'] ?? '',
'source_url' => $metadata['source_url'] ?? '',
'role_restriction' => $metadata['role_restriction'] ?? 'public',
'bot_id' => $bot_id,
'created_at' => $created_at,
'data_source' => 'pinecone'
);
$seen_ids[$match_id] = true;
}
}
}
}
usleep(200000); // 0.2 second delay
}
// Sort by created_at (newest first) and take top 1K
usort($all_records, function($a, $b) {
return $b->created_at - $a->created_at;
});
$recent_1k = array_slice($all_records, 0, 1000);
// Check for role restrictions stored in WordPress
$roles_table = $wpdb->prefix . 'mxchat_pinecone_roles';
foreach ($recent_1k as &$record) {
if (empty($record->role_restriction) || $record->role_restriction === 'public') {
$stored_role = $wpdb->get_var($wpdb->prepare(
"SELECT role_restriction FROM {$roles_table} WHERE vector_id = %s AND bot_id = %s",
$record->id,
$bot_id
));
if ($stored_role) {
$record->role_restriction = $stored_role;
}
}
}
//error_log('DEBUG: Found ' . count($all_records) . ' total unique records, returning top ' . count($recent_1k));
return $recent_1k;
} catch (Exception $e) {
//error_log('DEBUG: Exception in get_recent_1k_entries: ' . $e->getMessage());
return array();
}
}
/**
* Generate fixed query vectors for consistent results
*/
private function mxchat_generate_fixed_query_vectors() {
$dimensions = $this->mxchat_get_embedding_dimensions();
$vectors = array();
// Create 5 fixed vectors with different patterns for better coverage
$patterns = array(
'zeros_with_ones' => 0.1, // Mostly zeros with some 1s
'ascending' => 0.2, // Ascending pattern
'descending' => 0.3, // Descending pattern
'alternating' => 0.4, // Alternating positive/negative
'center_weighted' => 0.5 // Higher values in center
);
foreach ($patterns as $pattern_name => $seed) {
$vector = array();
for ($i = 0; $i < $dimensions; $i++) {
switch ($pattern_name) {
case 'zeros_with_ones':
$vector[] = ($i % 10 === 0) ? 1.0 : 0.0;
break;
case 'ascending':
$vector[] = ($i / $dimensions) * 2 - 1; // Range -1 to 1
break;
case 'descending':
$vector[] = (($dimensions - $i) / $dimensions) * 2 - 1;
break;
case 'alternating':
$vector[] = ($i % 2 === 0) ? $seed : -$seed;
break;
case 'center_weighted':
$center = $dimensions / 2;
$distance = abs($i - $center) / $center;
$vector[] = (1 - $distance) * $seed;
break;
}
}
// Normalize the vector to unit length
$magnitude = sqrt(array_sum(array_map(function($x) { return $x * $x; }, $vector)));
if ($magnitude > 0) {
$vector = array_map(function($x) use ($magnitude) { return $x / $magnitude; }, $vector);
}
$vectors[] = $vector;
}
return $vectors;
}
/**
* Scan Pinecone for processed content
*/
public function mxchat_scan_pinecone_for_processed_content($pinecone_options) {
$api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
$host = $pinecone_options['mxchat_pinecone_host'] ?? '';
if (empty($api_key) || empty($host)) {
return array();
}
try {
// Use multiple random vectors to get better coverage
$all_matches = array();
$seen_ids = array();
// Try 3 different random vectors to get better coverage
for ($i = 0; $i < 3; $i++) {
$query_url = "https://{$host}/query";
// Generate random vector with CORRECT dimensions
$random_vector = $this->mxchat_generate_random_vector();
$query_data = array(
'includeMetadata' => true,
'includeValues' => false,
'topK' => 10000,
'vector' => $random_vector
);
$response = wp_remote_post($query_url, array(
'headers' => array(
'Api-Key' => $api_key,
'Content-Type' => 'application/json'
),
'body' => json_encode($query_data),
'timeout' => 30
));
if (is_wp_error($response)) {
continue;
}
$response_code = wp_remote_retrieve_response_code($response);
if ($response_code !== 200) {
continue;
}
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if (isset($data['matches'])) {
foreach ($data['matches'] as $match) {
$match_id = $match['id'] ?? '';
if (!empty($match_id) && !isset($seen_ids[$match_id])) {
$all_matches[] = $match;
$seen_ids[$match_id] = true;
}
}
}
}
// Convert matches to processed data format
$processed_data = array();
foreach ($all_matches as $match) {
$metadata = $match['metadata'] ?? array();
$source_url = $metadata['source_url'] ?? '';
$match_id = $match['id'] ?? '';
if (!empty($source_url) && !empty($match_id)) {
$post_id = url_to_postid($source_url);
if ($post_id) {
$created_at = $metadata['created_at'] ?? '';
$processed_date = 'Recently';
if (!empty($created_at)) {
$timestamp = is_numeric($created_at) ? $created_at : strtotime($created_at);
if ($timestamp) {
$processed_date = human_time_diff($timestamp, current_time('timestamp')) . ' ago';
}
}
$processed_data[$post_id] = array(
'db_id' => $match_id,
'processed_date' => $processed_date,
'url' => $source_url,
'source' => 'pinecone',
'timestamp' => $timestamp ?? current_time('timestamp')
);
}
}
}
return $processed_data;
} catch (Exception $e) {
return array();
}
}
/**
* Get total count from Pinecone stats API
* UPDATED: Removed cache fallback reference
*/
private function mxchat_get_pinecone_total_count($pinecone_options, $bot_id = 'default') {
$api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
$host = $pinecone_options['mxchat_pinecone_host'] ?? '';
$namespace = $pinecone_options['mxchat_pinecone_namespace'] ?? '';
if (empty($api_key) || empty($host)) {
return 0;
}
try {
$stats_url = "https://{$host}/describe_index_stats";
$request_data = array();
// Add namespace if provided
if (!empty($namespace)) {
$request_data['namespace'] = $namespace;
}
$response = wp_remote_post($stats_url, array(
'headers' => array(
'Api-Key' => $api_key,
'Content-Type' => 'application/json'
),
'body' => json_encode($request_data),
'timeout' => 15
));
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
$body = wp_remote_retrieve_body($response);
$stats_data = json_decode($body, true);
$total_count = $stats_data['totalVectorCount'] ?? 0;
if ($total_count > 0) {
//error_log('DEBUG: Got total count from stats API: ' . $total_count);
return intval($total_count);
}
}
// If stats API fails, return 0 instead of using cache
return 0;
} catch (Exception $e) {
//error_log('DEBUG: Exception getting total count: ' . $e->getMessage());
return 0;
}
}
/**
* Get bot-specific Pinecone configuration for database operations
*/
public function mxchat_get_bot_pinecone_options($bot_id = 'default') {
//error_log('DEBUG: Getting Pinecone options for bot: ' . $bot_id);
// If default bot or multi-bot add-on not active, use default Pinecone config
if ($bot_id === 'default' || !class_exists('MxChat_Multi_Bot_Manager')) {
$addon_options = get_option('mxchat_pinecone_addon_options', array());
//error_log('DEBUG: Using default Pinecone options');
return $addon_options;
}
// Get bot-specific configuration using the filter
$bot_config = apply_filters('mxchat_get_bot_pinecone_config', array(), $bot_id);
//error_log('DEBUG: Bot config from filter: ' . print_r($bot_config, true));
// Check if we got valid bot-specific config
if (!empty($bot_config) && isset($bot_config['use_pinecone']) && $bot_config['use_pinecone']) {
// Convert bot config to the format expected by fetch functions
$pinecone_options = array(
'mxchat_use_pinecone' => '1',
'mxchat_pinecone_api_key' => $bot_config['api_key'] ?? '',
'mxchat_pinecone_host' => $bot_config['host'] ?? '',
'mxchat_pinecone_namespace' => $bot_config['namespace'] ?? '',
'mxchat_pinecone_environment' => '',
'mxchat_pinecone_index' => ''
);
//error_log('DEBUG: Returning bot-specific Pinecone options for bot: ' . $bot_id);
return $pinecone_options;
}
// Fallback to default options if bot-specific config is invalid
//error_log('DEBUG: Bot-specific config invalid, falling back to default');
return get_option('mxchat_pinecone_addon_options', array());
}
/**
* Get bot-specific Pinecone configuration
* Used in the knowledge retrieval functions
*/
private function get_bot_pinecone_config($bot_id = 'default') {
//error_log("MXCHAT DEBUG: get_bot_pinecone_config called for bot: " . $bot_id);
// If default bot or multi-bot add-on not active, use default Pinecone config
if ($bot_id === 'default' || !class_exists('MxChat_Multi_Bot_Manager')) {
//error_log("MXCHAT DEBUG: Using default Pinecone config (no multi-bot or bot is 'default')");
$addon_options = get_option('mxchat_pinecone_addon_options', array());
$config = array(
'use_pinecone' => (isset($addon_options['mxchat_use_pinecone']) && $addon_options['mxchat_use_pinecone'] === '1'),
'api_key' => $addon_options['mxchat_pinecone_api_key'] ?? '',
'host' => $addon_options['mxchat_pinecone_host'] ?? '',
'namespace' => $addon_options['mxchat_pinecone_namespace'] ?? ''
);
//error_log("MXCHAT DEBUG: Default config - use_pinecone: " . ($config['use_pinecone'] ? 'true' : 'false'));
return $config;
}
//error_log("MXCHAT DEBUG: Calling filter 'mxchat_get_bot_pinecone_config' for bot: " . $bot_id);
// Hook for multi-bot add-on to provide bot-specific Pinecone config
$bot_pinecone_config = apply_filters('mxchat_get_bot_pinecone_config', array(), $bot_id);
if (!empty($bot_pinecone_config)) {
//error_log("MXCHAT DEBUG: Got bot-specific config from filter");
//error_log(" - use_pinecone: " . (isset($bot_pinecone_config['use_pinecone']) ? ($bot_pinecone_config['use_pinecone'] ? 'true' : 'false') : 'not set'));
//error_log(" - host: " . ($bot_pinecone_config['host'] ?? 'not set'));
//error_log(" - namespace: " . ($bot_pinecone_config['namespace'] ?? 'not set'));
} else {
//error_log("MXCHAT DEBUG: Filter returned empty config!");
}
return is_array($bot_pinecone_config) ? $bot_pinecone_config : array();
}
/**
* Fetches vectors from Pinecone using provided IDs (for content selection feature)
*/
public function fetch_pinecone_vectors_by_ids($pinecone_options, $vector_ids) {
$api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
$host = $pinecone_options['mxchat_pinecone_host'] ?? '';
if (empty($api_key) || empty($host) || empty($vector_ids)) {
return array();
}
try {
$fetch_url = "https://{$host}/vectors/fetch";
// Pinecone fetch API allows fetching specific vectors by ID
$fetch_data = array(
'ids' => array_values($vector_ids)
);
$response = wp_remote_post($fetch_url, array(
'headers' => array(
'Api-Key' => $api_key,
'Content-Type' => 'application/json'
),
'body' => json_encode($fetch_data),
'timeout' => 30
));
if (is_wp_error($response)) {
return array();
}
$response_code = wp_remote_retrieve_response_code($response);
if ($response_code !== 200) {
return array();
}
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if (!isset($data['vectors'])) {
return array();
}
$processed_data = array();
foreach ($data['vectors'] as $vector_id => $vector_data) {
$metadata = $vector_data['metadata'] ?? array();
$source_url = $metadata['source_url'] ?? '';
if (!empty($source_url)) {
$post_id = url_to_postid($source_url);
if ($post_id) {
$created_at = $metadata['created_at'] ?? '';
$processed_date = 'Recently'; // Default
if (!empty($created_at)) {
$timestamp = is_numeric($created_at) ? $created_at : strtotime($created_at);
if ($timestamp) {
$processed_date = human_time_diff($timestamp, current_time('timestamp')) . ' ago';
}
}
$processed_data[$post_id] = array(
'db_id' => $vector_id,
'processed_date' => $processed_date,
'url' => $source_url,
'source' => 'pinecone',
'timestamp' => $timestamp ?? current_time('timestamp')
);
}
}
}
return $processed_data;
} catch (Exception $e) {
return array();
}
}
// ========================================
// PINECONE DELETE OPERATIONS
// ========================================
/**
* Delete all vectors from Pinecone
*/
public function mxchat_delete_all_from_pinecone($pinecone_options) {
$api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
$host = $pinecone_options['mxchat_pinecone_host'] ?? '';
if (empty($api_key) || empty($host)) {
return array(
'success' => false,
'message' => 'Missing Pinecone API credentials'
);
}
try {
// First, get all vector IDs by scanning Pinecone directly
$all_vector_ids = array();
// Get fresh data from Pinecone
$records = $this->mxchat_get_recent_1k_entries($pinecone_options);
foreach ($records as $record) {
if (!empty($record->id)) {
$all_vector_ids[] = $record->id;
}
}
if (empty($all_vector_ids)) {
return array(
'success' => true,
'message' => 'No vectors found to delete'
);
}
// Delete vectors in batches (Pinecone has limits on batch operations)
$batch_size = 100;
$batches = array_chunk($all_vector_ids, $batch_size);
$deleted_count = 0;
$failed_batches = 0;
foreach ($batches as $batch) {
$result = $this->mxchat_delete_pinecone_batch($batch, $api_key, $host);
if ($result['success']) {
$deleted_count += count($batch);
} else {
$failed_batches++;
}
}
if ($failed_batches > 0) {
return array(
'success' => false,
'message' => sprintf('Deleted %d vectors, but %d batches failed', $deleted_count, $failed_batches)
);
}
return array(
'success' => true,
'message' => "Successfully deleted {$deleted_count} vectors from Pinecone"
);
} catch (Exception $e) {
return array(
'success' => false,
'message' => $e->getMessage()
);
}
}
/**
* Deletes batch of vectors from Pinecone database
*/
private function mxchat_delete_pinecone_batch($vector_ids, $api_key, $host) {
// Build the API endpoint
$api_endpoint = "https://{$host}/vectors/delete";
// Prepare the request body with the IDs
$request_body = array(
'ids' => $vector_ids
);
// Make the deletion request
$response = wp_remote_post($api_endpoint, array(
'headers' => array(
'Api-Key' => $api_key,
'accept' => 'application/json',
'content-type' => 'application/json'
),
'body' => wp_json_encode($request_body),
'timeout' => 60, // Increased timeout for batch operations
'method' => 'POST'
));
// Handle WordPress HTTP API errors
if (is_wp_error($response)) {
return array(
'success' => false,
'message' => $response->get_error_message()
);
}
// Check response status
$response_code = wp_remote_retrieve_response_code($response);
$response_body = wp_remote_retrieve_body($response);
// Pinecone returns 200 for successful deletion
if ($response_code !== 200) {
//error_log('Pinecone batch deletion failed: HTTP ' . $response_code . ' - ' . $response_body);
return array(
'success' => false,
'message' => sprintf(
'Pinecone API error (HTTP %d): %s',
$response_code,
$response_body
)
);
}
return array(
'success' => true,
'message' => 'Batch deleted successfully from Pinecone'
);
}
/**
* Deletes vector from Pinecone using API request
*/
public function mxchat_delete_from_pinecone_by_vector_id($vector_id, $api_key, $host, $namespace = '') {
//error_log('=== PINECONE DELETE OPERATION ===');
//error_log('Vector ID: ' . $vector_id);
//error_log('Host: ' . $host);
//error_log('API Key: ' . (empty($api_key) ? 'EMPTY' : 'SET'));
// First, let's verify the vector exists before trying to delete
$fetch_url = "https://{$host}/vectors/fetch";
$fetch_params = array(
'ids' => array($vector_id)
);
// Add namespace if provided (though you said you're not using namespaces)
if (!empty($namespace)) {
$fetch_params['namespace'] = $namespace;
}
// Construct URL with query parameters for GET request
$fetch_url_with_params = $fetch_url . '?' . http_build_query($fetch_params);
$fetch_response = wp_remote_get($fetch_url_with_params, array(
'headers' => array(
'Api-Key' => $api_key,
'accept' => 'application/json'
),
'timeout' => 15
));
if (!is_wp_error($fetch_response) && wp_remote_retrieve_response_code($fetch_response) === 200) {
$fetch_body = wp_remote_retrieve_body($fetch_response);
$fetch_data = json_decode($fetch_body, true);
//error_log('DEBUG: Fetch response: ' . print_r($fetch_data, true));
if (isset($fetch_data['vectors']) && isset($fetch_data['vectors'][$vector_id])) {
//error_log('DEBUG: Vector EXISTS in this index before deletion');
} else {
//error_log('WARNING: Vector NOT FOUND in this index! It may be in a different bot\'s index');
// You might want to return an error here
}
} else {
//error_log('DEBUG: Could not fetch vector to verify existence');
}
// Now proceed with deletion
$api_endpoint = "https://{$host}/vectors/delete";
// Prepare the request body with the ID
$request_body = array(
'ids' => array($vector_id)
);
// Add namespace if provided
if (!empty($namespace)) {
$request_body['namespace'] = $namespace;
}
//error_log('DEBUG: Delete request body: ' . json_encode($request_body));
//error_log('DEBUG: Delete endpoint: ' . $api_endpoint);
// Make the deletion request
$response = wp_remote_post($api_endpoint, array(
'headers' => array(
'Api-Key' => $api_key,
'accept' => 'application/json',
'content-type' => 'application/json'
),
'body' => wp_json_encode($request_body),
'timeout' => 30
));
// Handle WordPress HTTP API errors
if (is_wp_error($response)) {
//error_log('DEBUG: WP Error: ' . $response->get_error_message());
return array(
'success' => false,
'message' => $response->get_error_message()
);
}
// Check response status
$response_code = wp_remote_retrieve_response_code($response);
$response_body = wp_remote_retrieve_body($response);
//error_log('DEBUG: Delete response code: ' . $response_code);
//error_log('DEBUG: Delete response body: ' . $response_body);
// Pinecone returns 200 for successful deletion (even if vector didn't exist)
if ($response_code !== 200) {
//error_log('DEBUG: Non-200 response from Pinecone');
return array(
'success' => false,
'message' => sprintf(
'Pinecone API error (HTTP %d): %s',
$response_code,
$response_body
)
);
}
// After deletion, verify it's actually gone
sleep(1); // Give Pinecone a moment to process
$verify_response = wp_remote_get($fetch_url_with_params, array(
'headers' => array(
'Api-Key' => $api_key,
'accept' => 'application/json'
),
'timeout' => 15
));
if (!is_wp_error($verify_response) && wp_remote_retrieve_response_code($verify_response) === 200) {
$verify_body = wp_remote_retrieve_body($verify_response);
$verify_data = json_decode($verify_body, true);
if (isset($verify_data['vectors']) && isset($verify_data['vectors'][$vector_id])) {
//error_log('ERROR: Vector STILL EXISTS after deletion attempt!');
return array(
'success' => false,
'message' => 'Vector still exists after deletion attempt'
);
} else {
//error_log('SUCCESS: Vector confirmed deleted (or never existed)');
}
}
//error_log('=== END PINECONE DELETE OPERATION ===');
return array(
'success' => true,
'message' => 'Vector deleted successfully from Pinecone'
);
}
/**
* Deletes data from Pinecone using a source URL
*/
public function mxchat_delete_from_pinecone_by_url($source_url, $pinecone_options) {
$host = $pinecone_options['mxchat_pinecone_host'] ?? '';
$api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
if (empty($host) || empty($api_key)) {
//error_log('MXChat: Pinecone deletion failed - missing configuration');
return false;
}
$api_endpoint = "https://{$host}/vectors/delete";
$vector_id = md5($source_url);
$request_body = array(
'ids' => array($vector_id)
);
$response = wp_remote_post($api_endpoint, array(
'headers' => array(
'Api-Key' => $api_key,
'accept' => 'application/json',
'content-type' => 'application/json'
),
'body' => wp_json_encode($request_body),
'timeout' => 30
));
if (is_wp_error($response)) {
//error_log('MXChat: Pinecone deletion error - ' . $response->get_error_message());
return false;
}
$response_code = wp_remote_retrieve_response_code($response);
if ($response_code !== 200) {
//error_log('MXChat: Pinecone deletion failed with status ' . $response_code);
return false;
}
return true;
}
/**
* Deletes data from Pinecone index using API key
*/
private function mxchat_delete_from_pinecone($urls, $api_key, $environment, $index_name) {
// Get the Pinecone host from options (matching your store_in_pinecone_main pattern)
$options = get_option('mxchat_pinecone_addon_options');
$host = $options['mxchat_pinecone_host'] ?? '';
if (empty($host)) {
return array(
'success' => false,
'message' => 'Pinecone host is not configured. Please set the host in your settings.'
);
}
// Build API endpoint using the configured host
$api_endpoint = "https://{$host}/vectors/delete";
// Create vector IDs from URLs (matching your store method's ID generation)
$vector_ids = array_map('md5', $urls);
// Prepare the delete request body
$request_body = array(
'ids' => $vector_ids,
'filter' => array(
'source_url' => array(
'$in' => $urls
)
)
);
// Make the deletion request
$response = wp_remote_post($api_endpoint, array(
'headers' => array(
'Api-Key' => $api_key,
'accept' => 'application/json',
'content-type' => 'application/json'
),
'body' => wp_json_encode($request_body),
'timeout' => 30,
'data_format' => 'body'
));
// Handle WordPress HTTP API errors
if (is_wp_error($response)) {
return array(
'success' => false,
'message' => $response->get_error_message()
);
}
// Check response status
$response_code = wp_remote_retrieve_response_code($response);
if ($response_code !== 200) {
$body = wp_remote_retrieve_body($response);
return array(
'success' => false,
'message' => sprintf(
'Pinecone API error (HTTP %d): %s',
$response_code,
$body
)
);
}
// Parse response body
$body = wp_remote_retrieve_body($response);
$response_data = json_decode($body, true);
// Final validation of the response
if (json_last_error() !== JSON_ERROR_NONE) {
return array(
'success' => false,
'message' => 'Failed to parse Pinecone response: ' . json_last_error_msg()
);
}
return array(
'success' => true,
'message' => sprintf('Successfully deleted %d vectors from Pinecone', count($vector_ids))
);
}
/**
* Retrieves processed content from Pinecone API
*/
public function mxchat_get_pinecone_processed_content($pinecone_options) {
$api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
$host = $pinecone_options['mxchat_pinecone_host'] ?? '';
if (empty($api_key) || empty($host)) {
return array();
}
$pinecone_data = array();
try {
// Always get fresh data from Pinecone
$pinecone_data = $this->mxchat_scan_pinecone_for_processed_content($pinecone_options);
// Method 2: Final fallback - try stats endpoint (if available)
if (empty($pinecone_data)) {
$stats_url = "https://{$host}/describe_index_stats";
$response = wp_remote_post($stats_url, array(
'headers' => array(
'Api-Key' => $api_key,
'Content-Type' => 'application/json'
),
'body' => json_encode(array()),
'timeout' => 30
));
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
$body = wp_remote_retrieve_body($response);
$stats_data = json_decode($body, true);
}
}
} catch (Exception $e) {
// Log error but don't return cached data
}
return $pinecone_data;
}
// ========================================
// HELPER METHODS
// ========================================
/**
* Validates Pinecone API credentials
*/
private function mxchat_validate_pinecone_credentials($api_key, $host) {
if (empty($api_key) || empty($host)) {
return false;
}
return true;
}
/**
* Get Pinecone API credentials from options
*/
private function mxchat_get_pinecone_credentials() {
$options = get_option('mxchat_options', array());
return array(
'api_key' => isset($options['pinecone_api_key']) ? $options['pinecone_api_key'] : '',
'host' => isset($options['pinecone_host']) ? $options['pinecone_host'] : ''
);
}
/**
* Log Pinecone operation errors
*/
private function log_pinecone_error($operation, $error_message) {
//error_log("MxChat Pinecone {$operation} Error: " . $error_message);
}
// ========================================
// STATIC ACCESS METHODS (for backward compatibility)
// ========================================
/**
* Get singleton instance
*/
public static function get_instance() {
static $instance = null;
if ($instance === null) {
$instance = new self();
}
return $instance;
}
}
// Initialize the Pinecone manager
$mxchat_pinecone_manager = MxChat_Pinecone_Manager::get_instance();