| @@ -177,15 +177,9 @@ | ||
| 177 | 177 | } |
| 178 | 178 | |
| 179 | 179 | // ===== FIXED: Generate unique identifier for manual content ===== |
| 180 | 180 | $original_source_url = $source_url; |
| 181 | - // Check if this is truly manual content (no URL at all) vs a real URL that filter_var rejects | |
| 182 | - // filter_var(FILTER_VALIDATE_URL) rejects valid URLs with encoded chars, non-ASCII, fragments, etc. | |
| 183 | - // Use a looser check: if it starts with http(s):// or has a scheme, it's a URL | |
| 184 | - $has_url_scheme = !empty($source_url) && preg_match('#^https?://#i', $source_url); | |
| 185 | - // Treat legacy mxchat.ai source URLs as manual — old bug assigned the site URL to manual entries | |
| 186 | - $is_legacy_mxchat_url = $has_url_scheme && strpos($source_url, 'mxchat.ai') !== false; | |
| 187 | - $is_manual_content = empty($source_url) || $source_url === '' || !$has_url_scheme || $is_legacy_mxchat_url; | |
| 181 | + $is_manual_content = empty($source_url) || $source_url === '' || !filter_var($source_url, FILTER_VALIDATE_URL); | |
| 188 | 182 | |
| 189 | 183 | if ($is_manual_content) { |
| 190 | 184 | // Generate unique identifier for manual content to prevent overwrites |
| 191 | 185 | $source_url = 'mxchat://manual-content/' . time() . '-' . wp_generate_password(8, false); |
| @@ -309,14 +303,14 @@ | ||
| 309 | 303 | // ===== UPDATED: Handle manual content with unique vector IDs ===== |
| 310 | 304 | if ($vector_id) { |
| 311 | 305 | // Use provided vector ID |
| 312 | 306 | //error_log('[MXCHAT-PINECONE-MAIN] Using provided vector ID: ' . $vector_id); |
| 313 | - } elseif (!empty($url) && preg_match('#^https?://#i', $url)) { | |
| 314 | - // For URLs, use URL-based ID (existing behavior) | |
| 307 | + } elseif (!empty($url) && filter_var($url, FILTER_VALIDATE_URL)) { | |
| 308 | + // For valid URLs, use URL-based ID (existing behavior) | |
| 315 | 309 | $vector_id = md5($url); |
| 316 | 310 | //error_log('[MXCHAT-PINECONE-MAIN] Generated vector ID from URL: ' . $vector_id); |
| 317 | 311 | } else { |
| 318 | - // For manual content (empty/no URL scheme), generate unique ID | |
| 312 | + // For manual content (empty/invalid URL), generate unique ID | |
| 319 | 313 | $vector_id = 'manual_' . time() . '_' . substr(md5($content . microtime(true)), 0, 8); |
| 320 | 314 | //error_log('[MXCHAT-PINECONE-MAIN] Generated unique vector ID for manual content: ' . $vector_id); |
| 321 | 315 | } |
| 322 | 316 | // ===== END UPDATE ===== |
| @@ -352,9 +346,9 @@ | ||
| 352 | 346 | // Fallback to old detection logic for backwards compatibility |
| 353 | 347 | $is_product = false; |
| 354 | 348 | $content_type = 'manual'; // Default for manual content |
| 355 | 349 | |
| 356 | - if (!empty($url) && preg_match('#^https?://#i', $url)) { | |
| 350 | + if (!empty($url) && filter_var($url, FILTER_VALIDATE_URL)) { | |
| 357 | 351 | $is_product = (strpos($url, '/product/') !== false || strpos($url, '/shop/') !== false); |
| 358 | 352 | $content_type = $is_product ? 'product' : 'content'; |
| 359 | 353 | } |
| 360 | 354 | } |
| @@ -804,34 +798,31 @@ | ||
| 804 | 798 | |
| 805 | 799 | // Add the original single-vector ID (for non-chunked content) |
| 806 | 800 | $vectors_to_delete[] = $base_vector_id; |
| 807 | 801 | |
| 808 | - // Pinecone /vectors/list is a GET endpoint with query-string parameters; a POST here returns a | |
| 809 | - // non-200 silently and we end up only deleting the base vector, leaving chunks orphaned. | |
| 810 | - $query_params = array( | |
| 802 | + // Use Pinecone list API to find all chunk vectors with this prefix | |
| 803 | + $list_url = "https://{$host}/vectors/list"; | |
| 804 | + | |
| 805 | + $list_body = array( | |
| 811 | 806 | 'prefix' => $base_vector_id . '_chunk_', |
| 812 | - 'limit' => 100, | |
| 807 | + 'limit' => 100 | |
| 813 | 808 | ); |
| 809 | + | |
| 814 | 810 | if (!empty($namespace)) { |
| 815 | - $query_params['namespace'] = $namespace; | |
| 811 | + $list_body['namespace'] = $namespace; | |
| 816 | 812 | } |
| 817 | 813 | |
| 818 | - $list_url = "https://{$host}/vectors/list?" . http_build_query($query_params); | |
| 814 | + $list_response = wp_remote_post($list_url, array( | |
| 815 | + 'headers' => array( | |
| 816 | + 'Api-Key' => $api_key, | |
| 817 | + 'accept' => 'application/json', | |
| 818 | + 'content-type' => 'application/json' | |
| 819 | + ), | |
| 820 | + 'body' => wp_json_encode($list_body), | |
| 821 | + 'timeout' => 30 | |
| 822 | + )); | |
| 819 | 823 | |
| 820 | - // Paginate in case a URL has more than 100 chunks. | |
| 821 | - do { | |
| 822 | - $list_response = wp_remote_get($list_url, array( | |
| 823 | - 'headers' => array( | |
| 824 | - 'Api-Key' => $api_key, | |
| 825 | - 'accept' => 'application/json', | |
| 826 | - ), | |
| 827 | - 'timeout' => 30, | |
| 828 | - )); | |
| 829 | - | |
| 830 | - if (is_wp_error($list_response) || wp_remote_retrieve_response_code($list_response) !== 200) { | |
| 831 | - break; | |
| 832 | - } | |
| 833 | - | |
| 824 | + if (!is_wp_error($list_response)) { | |
| 834 | 825 | $list_data = json_decode(wp_remote_retrieve_body($list_response), true); |
| 835 | 826 | if (!empty($list_data['vectors'])) { |
| 836 | 827 | foreach ($list_data['vectors'] as $vector) { |
| 837 | 828 | if (isset($vector['id'])) { |
| @@ -838,17 +829,9 @@ | ||
| 838 | 829 | $vectors_to_delete[] = $vector['id']; |
| 839 | 830 | } |
| 840 | 831 | } |
| 841 | 832 | } |
| 842 | - | |
| 843 | - $next_token = $list_data['pagination']['next'] ?? ''; | |
| 844 | - if (empty($next_token)) { | |
| 845 | - break; | |
| 846 | - } | |
| 847 | - | |
| 848 | - $query_params['paginationToken'] = $next_token; | |
| 849 | - $list_url = "https://{$host}/vectors/list?" . http_build_query($query_params); | |
| 850 | - } while (true); | |
| 833 | + } | |
| 851 | 834 | |
| 852 | 835 | if (empty($vectors_to_delete)) { |
| 853 | 836 | //error_log('[MXCHAT-CHUNK-DELETE] No vectors found to delete'); |
| 854 | 837 | return true; |