From fd505cbcea854e3ac6545cb48258acdd6470fd7c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 24 Apr 2026 08:27:33 +0000 Subject: [PATCH] Deploy 14a0f86 from main --- includes/class-psa-admin-page.php | 31 ++++-- includes/class-psa-admin.php | 115 ++++++++++++++------- includes/class-psa-ai-generator.php | 57 ++++++++--- includes/class-psa-batch-enrichment.php | 2 +- includes/class-psa-config.php | 14 +-- includes/class-psa-cost-reporter.php | 6 +- includes/class-psa-cost-tracker.php | 12 ++- includes/class-psa-directory-api.php | 130 +++++++++++++++--------- includes/class-psa-directory.php | 106 ++++++++++--------- includes/class-psa-encryption.php | 2 +- includes/class-psa-kb-builder.php | 8 +- includes/class-psa-openrouter.php | 40 ++++---- includes/class-psa-post-type-meta.php | 17 +++- includes/class-psa-post-type.php | 37 ++++--- includes/class-psa-search-handler.php | 65 ++++++++---- includes/class-psa-search.php | 17 +++- includes/class-psa-template.php | 2 +- peptide-search-ai.php | 34 +++---- readme.txt | 2 +- 19 files changed, 438 insertions(+), 259 deletions(-) diff --git a/includes/class-psa-admin-page.php b/includes/class-psa-admin-page.php index 4250995..383335c 100644 --- a/includes/class-psa-admin-page.php +++ b/includes/class-psa-admin-page.php @@ -29,10 +29,14 @@ class PSA_Admin_Page { } wp_enqueue_script( 'psa-batch-enrich', PSA_PLUGIN_URL . 'assets/js/psa-batch-enrich.js', array( 'jquery' ), PSA_VERSION, true ); - wp_localize_script( 'psa-batch-enrich', 'psaBatchEnrich', array( - 'ajaxUrl' => admin_url( 'admin-ajax.php' ), - 'nonce' => wp_create_nonce( 'psa_batch_enrich' ), - ) ); + wp_localize_script( + 'psa-batch-enrich', + 'psaBatchEnrich', + array( + 'ajaxUrl' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'psa_batch_enrich' ), + ) + ); } // ── Field renderers ───────────────────────────────────────────────── @@ -119,7 +123,11 @@ class PSA_Admin_Page {

- +

@@ -168,12 +176,12 @@ class PSA_Admin_Page { /** Render the API usage summary section with recent call log. */ private static function render_usage_summary(): void { - $current_spend = PSA_Cost_Tracker::get_monthly_spend(); - $current_tokens = PSA_Cost_Tracker::get_monthly_tokens(); - $estimated_count = PSA_Cost_Tracker::get_monthly_estimated_count(); - $budget = floatval( PSA_Admin::get_setting( 'monthly_budget', PSA_Config::DEFAULT_MONTHLY_BUDGET ) ); + $current_spend = PSA_Cost_Tracker::get_monthly_spend(); + $current_tokens = PSA_Cost_Tracker::get_monthly_tokens(); + $estimated_count = PSA_Cost_Tracker::get_monthly_estimated_count(); + $budget = floatval( PSA_Admin::get_setting( 'monthly_budget', PSA_Config::DEFAULT_MONTHLY_BUDGET ) ); $budget_remaining = ( 0.0 === $budget ) ? __( 'Unlimited', 'peptide-search-ai' ) : '$' . number_format( max( 0, $budget - $current_spend ), 2 ); - $recent_logs = PSA_Cost_Tracker::get_recent_logs( 20 ); + $recent_logs = PSA_Cost_Tracker::get_recent_logs( 20 ); ?>

@@ -216,6 +224,7 @@ class PSA_Admin_Page { - array( __CLASS__, 'sanitize_settings' ), - ) ); + register_setting( + 'psa_settings_group', + 'psa_settings', + array( + 'sanitize_callback' => array( __CLASS__, 'sanitize_settings' ), + ) + ); add_settings_section( 'psa_ai_section', __( 'OpenRouter API Settings', 'peptide-search-ai' ), array( 'PSA_Admin_Page', 'render_section_description' ), 'peptide-search-ai' ); add_settings_field( 'api_key', __( 'API Key', 'peptide-search-ai' ), array( 'PSA_Admin_Page', 'render_api_key_field' ), 'peptide-search-ai', 'psa_ai_section' ); @@ -95,18 +99,27 @@ class PSA_Admin { if ( 'migrate_categories' === $action && wp_verify_nonce( $nonce, 'psa_migrate_categories' ) ) { self::run_category_migration(); - add_action( 'admin_notices', function () { - echo '

' . esc_html__( 'Category migration completed.', 'peptide-search-ai' ) . '

'; - } ); + add_action( + 'admin_notices', + function () { + echo '

' . esc_html__( 'Category migration completed.', 'peptide-search-ai' ) . '

'; + } + ); } if ( 'reenrich' === $action && wp_verify_nonce( $nonce, 'psa_reenrich' ) ) { $queued = self::queue_reenrichment(); - add_action( 'admin_notices', function () use ( $queued ) { - echo '

' . esc_html( sprintf( - __( '%d peptides queued for re-enrichment.', 'peptide-search-ai' ), $queued - ) ) . '

'; - } ); + add_action( + 'admin_notices', + function () use ( $queued ) { + echo '

' . esc_html( + sprintf( + __( '%d peptides queued for re-enrichment.', 'peptide-search-ai' ), + $queued + ) + ) . '

'; + } + ); } } @@ -117,12 +130,14 @@ class PSA_Admin { * Idempotent — safe to run multiple times. */ private static function run_category_migration(): void { - $posts = get_posts( array( - 'post_type' => 'peptide', - 'post_status' => array( 'publish', 'draft' ), - 'posts_per_page' => -1, - 'fields' => 'ids', - ) ); + $posts = get_posts( + array( + 'post_type' => 'peptide', + 'post_status' => array( 'publish', 'draft' ), + 'posts_per_page' => -1, + 'fields' => 'ids', + ) + ); foreach ( $posts as $post_id ) { $terms = wp_get_object_terms( $post_id, 'peptide_category' ); @@ -199,17 +214,25 @@ class PSA_Admin { * @return int Number of peptides queued. */ private static function queue_reenrichment(): int { - $posts = get_posts( array( - 'post_type' => 'peptide', - 'post_status' => 'publish', - 'posts_per_page' => -1, - 'fields' => 'ids', - 'meta_query' => array( - 'relation' => 'OR', - array( 'key' => 'psa_half_life', 'compare' => 'NOT EXISTS' ), - array( 'key' => 'psa_half_life', 'value' => '' ), - ), - ) ); + $posts = get_posts( + array( + 'post_type' => 'peptide', + 'post_status' => 'publish', + 'posts_per_page' => -1, + 'fields' => 'ids', + 'meta_query' => array( + 'relation' => 'OR', + array( + 'key' => 'psa_half_life', + 'compare' => 'NOT EXISTS', + ), + array( + 'key' => 'psa_half_life', + 'value' => '', + ), + ), + ) + ); $daily_key = 'psa_daily_gen_' . gmdate( 'Y-m-d' ); $daily_count = (int) get_transient( $daily_key ); @@ -226,7 +249,12 @@ class PSA_Admin { } update_post_meta( $post_id, 'psa_source', 'pending' ); - wp_update_post( array( 'ID' => $post_id, 'post_status' => 'draft' ) ); + wp_update_post( + array( + 'ID' => $post_id, + 'post_status' => 'draft', + ) + ); update_post_meta( $post_id, 'psa_generation_started', time() ); $args = array( $post_id, $post->post_title ); @@ -234,7 +262,7 @@ class PSA_Admin { // Stagger 30s apart to avoid rate-limit hammering. wp_schedule_single_event( time() + ( $queued * 30 ), 'psa_generate_peptide_background', $args ); } - $queued++; + ++$queued; } set_transient( $daily_key, $daily_count + $queued, DAY_IN_SECONDS ); @@ -262,29 +290,38 @@ class PSA_Admin { // ── Backward-compatible proxies (rendering moved to PSA_Admin_Page) ─ /** @see PSA_Admin_Page::enqueue_batch_scripts() */ - public static function enqueue_batch_scripts( string $hook ): void { PSA_Admin_Page::enqueue_batch_scripts( $hook ); } + public static function enqueue_batch_scripts( string $hook ): void { + PSA_Admin_Page::enqueue_batch_scripts( $hook ); } /** @see PSA_Admin_Page::render_section_description() */ - public static function render_section_description(): void { PSA_Admin_Page::render_section_description(); } + public static function render_section_description(): void { + PSA_Admin_Page::render_section_description(); } /** @see PSA_Admin_Page::render_api_key_field() */ - public static function render_api_key_field(): void { PSA_Admin_Page::render_api_key_field(); } + public static function render_api_key_field(): void { + PSA_Admin_Page::render_api_key_field(); } /** @see PSA_Admin_Page::render_model_field() */ - public static function render_model_field(): void { PSA_Admin_Page::render_model_field(); } + public static function render_model_field(): void { + PSA_Admin_Page::render_model_field(); } /** @see PSA_Admin_Page::render_validation_model_field() */ - public static function render_validation_model_field(): void { PSA_Admin_Page::render_validation_model_field(); } + public static function render_validation_model_field(): void { + PSA_Admin_Page::render_validation_model_field(); } /** @see PSA_Admin_Page::render_publish_field() */ - public static function render_publish_field(): void { PSA_Admin_Page::render_publish_field(); } + public static function render_publish_field(): void { + PSA_Admin_Page::render_publish_field(); } /** @see PSA_Admin_Page::render_pubchem_field() */ - public static function render_pubchem_field(): void { PSA_Admin_Page::render_pubchem_field(); } + public static function render_pubchem_field(): void { + PSA_Admin_Page::render_pubchem_field(); } /** @see PSA_Admin_Page::render_budget_field() */ - public static function render_budget_field(): void { PSA_Admin_Page::render_budget_field(); } + public static function render_budget_field(): void { + PSA_Admin_Page::render_budget_field(); } /** @see PSA_Admin_Page::render_settings_page() */ - public static function render_settings_page(): void { PSA_Admin_Page::render_settings_page(); } + public static function render_settings_page(): void { + PSA_Admin_Page::render_settings_page(); } } diff --git a/includes/class-psa-ai-generator.php b/includes/class-psa-ai-generator.php index 7e683c9..922f723 100755 --- a/includes/class-psa-ai-generator.php +++ b/includes/class-psa-ai-generator.php @@ -35,7 +35,11 @@ class PSA_AI_Generator { */ public static function validate_peptide_name( string $name ) { if ( ! self::validate_peptide_input( $name ) ) { - return array( 'is_valid' => false, 'canonical_name' => $name, 'reason' => 'Invalid characters in peptide name.' ); + return array( + 'is_valid' => false, + 'canonical_name' => $name, + 'reason' => 'Invalid characters in peptide name.', + ); } $options = self::get_settings(); @@ -70,7 +74,11 @@ class PSA_AI_Generator { $data = PSA_OpenRouter::parse_response( $response ); if ( is_wp_error( $data ) ) { - return array( 'is_valid' => false, 'canonical_name' => $name, 'reason' => 'Could not verify this peptide name.' ); + return array( + 'is_valid' => false, + 'canonical_name' => $name, + 'reason' => 'Could not verify this peptide name.', + ); } $result = array( @@ -136,12 +144,15 @@ class PSA_AI_Generator { } $post_status = ( 'publish' === $options['auto_publish'] ) ? 'publish' : 'draft'; - $update_result = wp_update_post( array( - 'ID' => $post_id, - 'post_title' => sanitize_text_field( $result['name'] ?? $peptide_name ), - 'post_content' => wp_kses_post( $post_content ), - 'post_status' => $post_status, - ), true ); + $update_result = wp_update_post( + array( + 'ID' => $post_id, + 'post_title' => sanitize_text_field( $result['name'] ?? $peptide_name ), + 'post_content' => wp_kses_post( $post_content ), + 'post_status' => $post_status, + ), + true + ); if ( is_wp_error( $update_result ) ) { error_log( 'PSA: wp_update_post FAILED for post ' . $post_id . ': ' . $update_result->get_error_message() ); @@ -214,8 +225,14 @@ class PSA_AI_Generator { $gen_cost = PSA_Cost_Tracker::estimate_cost( $generation_model, $gen_tokens, PSA_Config::GENERATION_MAX_TOKENS ); return array( - 'validation' => array( 'tokens' => $val_tokens + PSA_Config::VALIDATION_MAX_TOKENS, 'estimated_cost_usd' => $val_cost ), - 'generation' => array( 'tokens' => $gen_tokens + PSA_Config::GENERATION_MAX_TOKENS, 'estimated_cost_usd' => $gen_cost ), + 'validation' => array( + 'tokens' => $val_tokens + PSA_Config::VALIDATION_MAX_TOKENS, + 'estimated_cost_usd' => $val_cost, + ), + 'generation' => array( + 'tokens' => $gen_tokens + PSA_Config::GENERATION_MAX_TOKENS, + 'estimated_cost_usd' => $gen_cost, + ), 'total_estimated_cost_usd' => $val_cost + $gen_cost, ); } @@ -228,7 +245,13 @@ class PSA_AI_Generator { * @return array */ private static function get_settings(): array { - $defaults = array( 'api_key' => '', 'ai_model' => '', 'validation_model' => '', 'auto_publish' => 'draft', 'use_pubchem' => '1' ); + $defaults = array( + 'api_key' => '', + 'ai_model' => '', + 'validation_model' => '', + 'auto_publish' => 'draft', + 'use_pubchem' => '1', + ); $settings = wp_parse_args( get_option( 'psa_settings', array() ), $defaults ); if ( ! empty( $settings['api_key'] ) && ! ( defined( 'PSA_OPENROUTER_KEY' ) && PSA_OPENROUTER_KEY ) ) { @@ -269,14 +292,18 @@ class PSA_AI_Generator { // ── Backward-compatible proxies ───────────────────────────────────── /** @see PSA_AI_Content::build_validation_prompt() */ - public static function build_validation_prompt( string $name ): string { return PSA_AI_Content::build_validation_prompt( $name ); } + public static function build_validation_prompt( string $name ): string { + return PSA_AI_Content::build_validation_prompt( $name ); } /** @see PSA_AI_Content::build_generation_prompt() */ - public static function build_generation_prompt( string $peptide_name ): string { return PSA_AI_Content::build_generation_prompt( $peptide_name ); } + public static function build_generation_prompt( string $peptide_name ): string { + return PSA_AI_Content::build_generation_prompt( $peptide_name ); } /** @see PSA_AI_Content::save_peptide_meta() */ - public static function save_peptide_meta( int $post_id, array $ai_data, ?array $pubchem_data ): void { PSA_AI_Content::save_peptide_meta( $post_id, $ai_data, $pubchem_data ); } + public static function save_peptide_meta( int $post_id, array $ai_data, ?array $pubchem_data ): void { + PSA_AI_Content::save_peptide_meta( $post_id, $ai_data, $pubchem_data ); } /** @see PSA_AI_Content::assign_category_term() */ - public static function assign_category_term( int $post_id, array $ai_data ): void { PSA_AI_Content::assign_category_term( $post_id, $ai_data ); } + public static function assign_category_term( int $post_id, array $ai_data ): void { + PSA_AI_Content::assign_category_term( $post_id, $ai_data ); } } diff --git a/includes/class-psa-batch-enrichment.php b/includes/class-psa-batch-enrichment.php index 32c4189..7df932f 100755 --- a/includes/class-psa-batch-enrichment.php +++ b/includes/class-psa-batch-enrichment.php @@ -106,7 +106,7 @@ class PSA_Batch_Enrichment { PSA_AI_Generator::background_generate( $post_id, $peptide_name ); // Check outcome: did generation succeed? - $error = get_post_meta( $post_id, 'psa_generation_error', true ); + $error = get_post_meta( $post_id, 'psa_generation_error', true ); $refreshed_post = get_post( $post_id ); // If still in draft (generation failed or auto_publish is 'draft'), force republish. diff --git a/includes/class-psa-config.php b/includes/class-psa-config.php index 1fd1ec1..c22f19b 100755 --- a/includes/class-psa-config.php +++ b/includes/class-psa-config.php @@ -25,23 +25,23 @@ class PSA_Config { const AJAX_RATE_LIMIT = 10; // Daily generation cap - const DAILY_GENERATION_CAP = 50; + const DAILY_GENERATION_CAP = 50; // Generation timing const PENDING_TIMEOUT = 300; // seconds (5 minutes) const MAX_GENERATION_RETRIES = 3; // AI token limits - const VALIDATION_MAX_TOKENS = 300; - const GENERATION_MAX_TOKENS = 4096; + const VALIDATION_MAX_TOKENS = 300; + const GENERATION_MAX_TOKENS = 4096; // Cache TTL - const VALIDATION_CACHE_TTL = null; // Set to DAY_IN_SECONDS at runtime - const PUBCHEM_CACHE_TTL = null; // Set to 7 * DAY_IN_SECONDS at runtime + const VALIDATION_CACHE_TTL = null; // Set to DAY_IN_SECONDS at runtime + const PUBCHEM_CACHE_TTL = null; // Set to 7 * DAY_IN_SECONDS at runtime // API retry settings - const API_RETRY_MAX = 3; - const API_RETRY_BASE_DELAY = 5; // seconds + const API_RETRY_MAX = 3; + const API_RETRY_BASE_DELAY = 5; // seconds // Cost tracking and budget const DEFAULT_MONTHLY_BUDGET = 0; // 0 = unlimited diff --git a/includes/class-psa-cost-reporter.php b/includes/class-psa-cost-reporter.php index 148eab1..88f50bc 100644 --- a/includes/class-psa-cost-reporter.php +++ b/includes/class-psa-cost-reporter.php @@ -29,7 +29,7 @@ class PSA_Cost_Reporter { public static function get_monthly_spend( ?int $year = null, ?int $month = null ): float { global $wpdb; list( $start, $end ) = self::month_range( $year, $month ); - $table = $wpdb->prefix . 'psa_api_logs'; + $table = $wpdb->prefix . 'psa_api_logs'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery $result = $wpdb->get_var( @@ -53,7 +53,7 @@ class PSA_Cost_Reporter { public static function get_monthly_tokens( ?int $year = null, ?int $month = null ): int { global $wpdb; list( $start, $end ) = self::month_range( $year, $month ); - $table = $wpdb->prefix . 'psa_api_logs'; + $table = $wpdb->prefix . 'psa_api_logs'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery $result = $wpdb->get_var( @@ -80,7 +80,7 @@ class PSA_Cost_Reporter { public static function get_monthly_estimated_count( ?int $year = null, ?int $month = null ): int { global $wpdb; list( $start, $end ) = self::month_range( $year, $month ); - $table = $wpdb->prefix . 'psa_api_logs'; + $table = $wpdb->prefix . 'psa_api_logs'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery $result = $wpdb->get_var( diff --git a/includes/class-psa-cost-tracker.php b/includes/class-psa-cost-tracker.php index 6eec5d9..7470db7 100755 --- a/includes/class-psa-cost-tracker.php +++ b/includes/class-psa-cost-tracker.php @@ -158,14 +158,18 @@ class PSA_Cost_Tracker { // ── Backward-compatible proxies (reporting moved to PSA_Cost_Reporter) ── /** @see PSA_Cost_Reporter::get_monthly_spend() */ - public static function get_monthly_spend( ?int $year = null, ?int $month = null ): float { return PSA_Cost_Reporter::get_monthly_spend( $year, $month ); } + public static function get_monthly_spend( ?int $year = null, ?int $month = null ): float { + return PSA_Cost_Reporter::get_monthly_spend( $year, $month ); } /** @see PSA_Cost_Reporter::get_monthly_tokens() */ - public static function get_monthly_tokens( ?int $year = null, ?int $month = null ): int { return PSA_Cost_Reporter::get_monthly_tokens( $year, $month ); } + public static function get_monthly_tokens( ?int $year = null, ?int $month = null ): int { + return PSA_Cost_Reporter::get_monthly_tokens( $year, $month ); } /** @see PSA_Cost_Reporter::get_monthly_estimated_count() */ - public static function get_monthly_estimated_count( ?int $year = null, ?int $month = null ): int { return PSA_Cost_Reporter::get_monthly_estimated_count( $year, $month ); } + public static function get_monthly_estimated_count( ?int $year = null, ?int $month = null ): int { + return PSA_Cost_Reporter::get_monthly_estimated_count( $year, $month ); } /** @see PSA_Cost_Reporter::get_recent_logs() */ - public static function get_recent_logs( int $limit = 20 ): array { return PSA_Cost_Reporter::get_recent_logs( $limit ); } + public static function get_recent_logs( int $limit = 20 ): array { + return PSA_Cost_Reporter::get_recent_logs( $limit ); } } diff --git a/includes/class-psa-directory-api.php b/includes/class-psa-directory-api.php index 1abbad7..3d47b76 100644 --- a/includes/class-psa-directory-api.php +++ b/includes/class-psa-directory-api.php @@ -23,18 +23,45 @@ class PSA_Directory_API { * Register the /v1/compounds endpoint. Public (no auth) — rate limiting at server/CDN level. */ public static function register_rest_routes(): void { - register_rest_route( 'peptide-search-ai/v1', '/compounds', array( - 'methods' => 'GET', - 'callback' => array( __CLASS__, 'rest_compounds' ), - 'permission_callback' => '__return_true', - 'args' => array( - 'search' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '' ), - 'category' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '' ), - 'fields' => array( 'type' => 'string', 'enum' => array( 'basic', 'full' ), 'default' => 'full' ), - 'page' => array( 'type' => 'integer', 'default' => 1, 'minimum' => 1, 'sanitize_callback' => 'absint' ), - 'per_page' => array( 'type' => 'integer', 'default' => PSA_Directory::PER_PAGE_DEFAULT, 'minimum' => 1, 'maximum' => PSA_Directory::PER_PAGE_MAX, 'sanitize_callback' => 'absint' ), - ), - ) ); + register_rest_route( + 'peptide-search-ai/v1', + '/compounds', + array( + 'methods' => 'GET', + 'callback' => array( __CLASS__, 'rest_compounds' ), + 'permission_callback' => '__return_true', + 'args' => array( + 'search' => array( + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'default' => '', + ), + 'category' => array( + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'default' => '', + ), + 'fields' => array( + 'type' => 'string', + 'enum' => array( 'basic', 'full' ), + 'default' => 'full', + ), + 'page' => array( + 'type' => 'integer', + 'default' => 1, + 'minimum' => 1, + 'sanitize_callback' => 'absint', + ), + 'per_page' => array( + 'type' => 'integer', + 'default' => PSA_Directory::PER_PAGE_DEFAULT, + 'minimum' => 1, + 'maximum' => PSA_Directory::PER_PAGE_MAX, + 'sanitize_callback' => 'absint', + ), + ), + ) + ); } /** @@ -65,11 +92,13 @@ class PSA_Directory_API { } if ( ! empty( $category ) ) { - $query_args['tax_query'] = array( array( - 'taxonomy' => 'peptide_category', - 'field' => 'slug', - 'terms' => $category, - ) ); + $query_args['tax_query'] = array( + array( + 'taxonomy' => 'peptide_category', + 'field' => 'slug', + 'terms' => $category, + ), + ); } $query = new WP_Query( $query_args ); @@ -83,13 +112,16 @@ class PSA_Directory_API { $compounds[] = self::format_compound( $post, $fields ); } - $response = new WP_REST_Response( array( - 'compounds' => $compounds, - 'total' => (int) $query->found_posts, - 'total_pages' => (int) $query->max_num_pages, - 'page' => $page, - 'per_page' => $per_page, - ), 200 ); + $response = new WP_REST_Response( + array( + 'compounds' => $compounds, + 'total' => (int) $query->found_posts, + 'total_pages' => (int) $query->max_num_pages, + 'page' => $page, + 'per_page' => $per_page, + ), + 200 + ); $response->header( 'X-WP-Total', (string) $query->found_posts ); $response->header( 'X-WP-TotalPages', (string) $query->max_num_pages ); @@ -111,7 +143,10 @@ class PSA_Directory_API { $categories = array(); if ( ! is_wp_error( $terms ) ) { foreach ( $terms as $term ) { - $categories[] = array( 'slug' => $term->slug, 'name' => $term->name ); + $categories[] = array( + 'slug' => $term->slug, + 'name' => $term->name, + ); } } @@ -132,27 +167,30 @@ class PSA_Directory_API { return $basic; } - $full = array_merge( $basic, array( - 'excerpt' => wp_trim_words( $post->post_content, 30 ), - 'description' => wp_trim_words( $post->post_content, 80 ), - 'sequence' => get_post_meta( $id, 'psa_sequence', true ), - 'molecular_weight' => get_post_meta( $id, 'psa_molecular_weight', true ), - 'molecular_formula' => get_post_meta( $id, 'psa_molecular_formula', true ), - 'aliases' => get_post_meta( $id, 'psa_aliases', true ), - 'mechanism' => get_post_meta( $id, 'psa_mechanism', true ), - 'research_apps' => get_post_meta( $id, 'psa_research_apps', true ), - 'safety_profile' => get_post_meta( $id, 'psa_safety_profile', true ), - 'dosage_info' => get_post_meta( $id, 'psa_dosage_info', true ), - 'references' => get_post_meta( $id, 'psa_references', true ), - 'pubchem_cid' => get_post_meta( $id, 'psa_pubchem_cid', true ), - 'solubility' => get_post_meta( $id, 'psa_solubility', true ), - 'vial_size_mg' => get_post_meta( $id, 'psa_vial_size_mg', true ), - 'storage_lyophilized' => get_post_meta( $id, 'psa_storage_lyophilized', true ), - 'storage_reconstituted' => get_post_meta( $id, 'psa_storage_reconstituted', true ), - 'typical_dose_mcg' => get_post_meta( $id, 'psa_typical_dose_mcg', true ), - 'cycle_parameters' => get_post_meta( $id, 'psa_cycle_parameters', true ), - 'amino_acid_count' => get_post_meta( $id, 'psa_amino_acid_count', true ), - ) ); + $full = array_merge( + $basic, + array( + 'excerpt' => wp_trim_words( $post->post_content, 30 ), + 'description' => wp_trim_words( $post->post_content, 80 ), + 'sequence' => get_post_meta( $id, 'psa_sequence', true ), + 'molecular_weight' => get_post_meta( $id, 'psa_molecular_weight', true ), + 'molecular_formula' => get_post_meta( $id, 'psa_molecular_formula', true ), + 'aliases' => get_post_meta( $id, 'psa_aliases', true ), + 'mechanism' => get_post_meta( $id, 'psa_mechanism', true ), + 'research_apps' => get_post_meta( $id, 'psa_research_apps', true ), + 'safety_profile' => get_post_meta( $id, 'psa_safety_profile', true ), + 'dosage_info' => get_post_meta( $id, 'psa_dosage_info', true ), + 'references' => get_post_meta( $id, 'psa_references', true ), + 'pubchem_cid' => get_post_meta( $id, 'psa_pubchem_cid', true ), + 'solubility' => get_post_meta( $id, 'psa_solubility', true ), + 'vial_size_mg' => get_post_meta( $id, 'psa_vial_size_mg', true ), + 'storage_lyophilized' => get_post_meta( $id, 'psa_storage_lyophilized', true ), + 'storage_reconstituted' => get_post_meta( $id, 'psa_storage_reconstituted', true ), + 'typical_dose_mcg' => get_post_meta( $id, 'psa_typical_dose_mcg', true ), + 'cycle_parameters' => get_post_meta( $id, 'psa_cycle_parameters', true ), + 'amino_acid_count' => get_post_meta( $id, 'psa_amino_acid_count', true ), + ) + ); return apply_filters( 'psa_rest_compound_data', $full, $id ); } diff --git a/includes/class-psa-directory.php b/includes/class-psa-directory.php index 3a57d8c..b5f309e 100755 --- a/includes/class-psa-directory.php +++ b/includes/class-psa-directory.php @@ -38,57 +38,66 @@ class PSA_Directory { * @return string HTML output. */ public static function render_shortcode( $atts ): string { - $terms = get_terms( array( - 'taxonomy' => 'peptide_category', - 'hide_empty' => false, - 'orderby' => 'name', - 'order' => 'ASC', - ) ); + $terms = get_terms( + array( + 'taxonomy' => 'peptide_category', + 'hide_empty' => false, + 'orderby' => 'name', + 'order' => 'ASC', + ) + ); $categories = array(); if ( ! is_wp_error( $terms ) ) { foreach ( $terms as $term ) { - $categories[] = array( 'slug' => $term->slug, 'name' => $term->name ); + $categories[] = array( + 'slug' => $term->slug, + 'name' => $term->name, + ); } } - wp_localize_script( 'psa-directory', 'psaDirectory', array( - 'restUrl' => esc_url_raw( rest_url( 'peptide-search-ai/v1/compounds' ) ), - 'nonce' => wp_create_nonce( 'wp_rest' ), - 'categories' => $categories, - 'perPage' => self::PER_PAGE_DEFAULT, - 'i18n' => array( - 'title' => __( 'Peptide Directory', 'peptide-search-ai' ), - 'subtitle' => __( 'Browse reagents with detailed research parameters.', 'peptide-search-ai' ), - 'searchPlaceholder' => __( 'Search peptides...', 'peptide-search-ai' ), - 'showAll' => __( 'Show All', 'peptide-search-ai' ), - 'loadMore' => __( 'Load More', 'peptide-search-ai' ), - 'loading' => __( 'Loading...', 'peptide-search-ai' ), - 'noResults' => __( 'No peptides found matching your criteria.', 'peptide-search-ai' ), - 'viewDetails' => __( 'View Research Details', 'peptide-search-ai' ), - 'viewFullPage' => __( 'View Full Page', 'peptide-search-ai' ), - 'close' => __( 'Close', 'peptide-search-ai' ), - 'halfLife' => __( 'Half-Life', 'peptide-search-ai' ), - 'stability' => __( 'Stability', 'peptide-search-ai' ), - 'solubility' => __( 'Solubility', 'peptide-search-ai' ), - 'vialSize' => __( 'Vial Size', 'peptide-search-ai' ), - 'storageLyo' => __( 'Storage (Lyophilized)', 'peptide-search-ai' ), - 'storageRecon' => __( 'Storage (Reconstituted)', 'peptide-search-ai' ), - 'molecularWeight' => __( 'Molecular Weight', 'peptide-search-ai' ), - 'formula' => __( 'Formula', 'peptide-search-ai' ), - 'typicalDose' => __( 'Typical Dose', 'peptide-search-ai' ), - 'cycleParams' => __( 'Cycle Parameters', 'peptide-search-ai' ), - 'sequence' => __( 'Sequence', 'peptide-search-ai' ), - 'singleReagents' => __( 'Single Reagents', 'peptide-search-ai' ), - 'protocolModels' => __( 'Protocol Models', 'peptide-search-ai' ), - 'comingSoon' => __( 'Coming Soon', 'peptide-search-ai' ), - 'researchParams' => __( 'Research Parameters', 'peptide-search-ai' ), - 'communityObs' => __( 'Community Observations', 'peptide-search-ai' ), - 'pubchemLink' => __( 'View on PubChem', 'peptide-search-ai' ), - 'copySequence' => __( 'Copy', 'peptide-search-ai' ), - 'copied' => __( 'Copied!', 'peptide-search-ai' ), - ), - ) ); + wp_localize_script( + 'psa-directory', + 'psaDirectory', + array( + 'restUrl' => esc_url_raw( rest_url( 'peptide-search-ai/v1/compounds' ) ), + 'nonce' => wp_create_nonce( 'wp_rest' ), + 'categories' => $categories, + 'perPage' => self::PER_PAGE_DEFAULT, + 'i18n' => array( + 'title' => __( 'Peptide Directory', 'peptide-search-ai' ), + 'subtitle' => __( 'Browse reagents with detailed research parameters.', 'peptide-search-ai' ), + 'searchPlaceholder' => __( 'Search peptides...', 'peptide-search-ai' ), + 'showAll' => __( 'Show All', 'peptide-search-ai' ), + 'loadMore' => __( 'Load More', 'peptide-search-ai' ), + 'loading' => __( 'Loading...', 'peptide-search-ai' ), + 'noResults' => __( 'No peptides found matching your criteria.', 'peptide-search-ai' ), + 'viewDetails' => __( 'View Research Details', 'peptide-search-ai' ), + 'viewFullPage' => __( 'View Full Page', 'peptide-search-ai' ), + 'close' => __( 'Close', 'peptide-search-ai' ), + 'halfLife' => __( 'Half-Life', 'peptide-search-ai' ), + 'stability' => __( 'Stability', 'peptide-search-ai' ), + 'solubility' => __( 'Solubility', 'peptide-search-ai' ), + 'vialSize' => __( 'Vial Size', 'peptide-search-ai' ), + 'storageLyo' => __( 'Storage (Lyophilized)', 'peptide-search-ai' ), + 'storageRecon' => __( 'Storage (Reconstituted)', 'peptide-search-ai' ), + 'molecularWeight' => __( 'Molecular Weight', 'peptide-search-ai' ), + 'formula' => __( 'Formula', 'peptide-search-ai' ), + 'typicalDose' => __( 'Typical Dose', 'peptide-search-ai' ), + 'cycleParams' => __( 'Cycle Parameters', 'peptide-search-ai' ), + 'sequence' => __( 'Sequence', 'peptide-search-ai' ), + 'singleReagents' => __( 'Single Reagents', 'peptide-search-ai' ), + 'protocolModels' => __( 'Protocol Models', 'peptide-search-ai' ), + 'comingSoon' => __( 'Coming Soon', 'peptide-search-ai' ), + 'researchParams' => __( 'Research Parameters', 'peptide-search-ai' ), + 'communityObs' => __( 'Community Observations', 'peptide-search-ai' ), + 'pubchemLink' => __( 'View on PubChem', 'peptide-search-ai' ), + 'copySequence' => __( 'Copy', 'peptide-search-ai' ), + 'copied' => __( 'Copied!', 'peptide-search-ai' ), + ), + ) + ); ob_start(); ?> @@ -104,11 +113,14 @@ class PSA_Directory { // ── Backward-compatible proxies (REST moved to PSA_Directory_API) ─── /** @see PSA_Directory_API::register_rest_routes() */ - public static function register_rest_routes(): void { PSA_Directory_API::register_rest_routes(); } + public static function register_rest_routes(): void { + PSA_Directory_API::register_rest_routes(); } /** @see PSA_Directory_API::rest_compounds() */ - public static function rest_compounds( $request ) { return PSA_Directory_API::rest_compounds( $request ); } + public static function rest_compounds( $request ) { + return PSA_Directory_API::rest_compounds( $request ); } /** @see PSA_Directory_API::format_compound() */ - public static function format_compound( $post, string $fields = 'full' ): array { return PSA_Directory_API::format_compound( $post, $fields ); } + public static function format_compound( $post, string $fields = 'full' ): array { + return PSA_Directory_API::format_compound( $post, $fields ); } } diff --git a/includes/class-psa-encryption.php b/includes/class-psa-encryption.php index c21b2a7..a407fae 100755 --- a/includes/class-psa-encryption.php +++ b/includes/class-psa-encryption.php @@ -79,7 +79,7 @@ class PSA_Encryption { } // First 16 bytes are the IV. - $iv = substr( $data, 0, 16 ); + $iv = substr( $data, 0, 16 ); $encrypted_data = substr( $data, 16 ); // Decrypt. diff --git a/includes/class-psa-kb-builder.php b/includes/class-psa-kb-builder.php index e23f4c3..baf323e 100755 --- a/includes/class-psa-kb-builder.php +++ b/includes/class-psa-kb-builder.php @@ -47,7 +47,7 @@ class PSA_KB_Builder { return; } - $title = sanitize_text_field( $ai_data['name'] ?? $peptide_name ); + $title = sanitize_text_field( $ai_data['name'] ?? $peptide_name ); $existing = get_posts( array( 'post_type' => 'epkb_post_type_1', @@ -124,10 +124,10 @@ class PSA_KB_Builder { // Research & Potential Benefits (with h3 subheadings). if ( ! empty( $ai_data['research_benefits'] ) || ! empty( $ai_data['research_applications'] ) ) { - $c .= '

Research & Potential Benefits

' . "\n"; - $c .= '

The following potential benefits are based on animal studies and preclinical research. Human clinical trial data is extremely limited.

' . "\n"; + $c .= '

Research & Potential Benefits

' . "\n"; + $c .= '

The following potential benefits are based on animal studies and preclinical research. Human clinical trial data is extremely limited.

' . "\n"; $research = $ai_data['research_benefits'] ?? $ai_data['research_applications'] ?? ''; - $c .= self::research_with_subheadings( $research ); + $c .= self::research_with_subheadings( $research ); } // Administration & Dosing. diff --git a/includes/class-psa-openrouter.php b/includes/class-psa-openrouter.php index 5c9c6bc..312a479 100755 --- a/includes/class-psa-openrouter.php +++ b/includes/class-psa-openrouter.php @@ -90,15 +90,15 @@ class PSA_OpenRouter { array( 'timeout' => 120, 'headers' => array( - 'Content-Type' => 'application/json', - 'Authorization' => 'Bearer ' . $api_key, - 'HTTP-Referer' => $site_url, - 'X-Title' => $site_name, + 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer ' . $api_key, + 'HTTP-Referer' => $site_url, + 'X-Title' => $site_name, ), - 'body' => wp_json_encode( + 'body' => wp_json_encode( array( - 'model' => $model, - 'messages' => array( + 'model' => $model, + 'messages' => array( array( 'role' => 'system', 'content' => 'You are a scientific database assistant. Return only valid JSON.', @@ -108,8 +108,8 @@ class PSA_OpenRouter { 'content' => $prompt, ), ), - 'max_tokens' => $max_tokens, - 'temperature' => 0.3, + 'max_tokens' => $max_tokens, + 'temperature' => 0.3, ) ), ) @@ -147,10 +147,10 @@ class PSA_OpenRouter { } // Log API call with token usage. - $response_text = $body['choices'][0]['message']['content']; - $prompt_tokens = (int) ( $body['usage']['prompt_tokens'] ?? 0 ); + $response_text = $body['choices'][0]['message']['content']; + $prompt_tokens = (int) ( $body['usage']['prompt_tokens'] ?? 0 ); $completion_tokens = (int) ( $body['usage']['completion_tokens'] ?? 0 ); - $token_source = 'api'; + $token_source = 'api'; // Diagnostic: log the raw usage structure once per model so we can verify // whether the provider returns token counts. Remove after confirming. @@ -172,14 +172,14 @@ class PSA_OpenRouter { PSA_Cost_Tracker::log_api_call( array( - 'provider' => 'openrouter', - 'model' => $model, - 'prompt_tokens' => $prompt_tokens, - 'completion_tokens' => $completion_tokens, - 'request_type' => $request_type, - 'peptide_name' => $peptide_name, - 'success' => true, - 'token_source' => $token_source, + 'provider' => 'openrouter', + 'model' => $model, + 'prompt_tokens' => $prompt_tokens, + 'completion_tokens' => $completion_tokens, + 'request_type' => $request_type, + 'peptide_name' => $peptide_name, + 'success' => true, + 'token_source' => $token_source, ) ); diff --git a/includes/class-psa-post-type-meta.php b/includes/class-psa-post-type-meta.php index 7f21480..530be31 100644 --- a/includes/class-psa-post-type-meta.php +++ b/includes/class-psa-post-type-meta.php @@ -47,8 +47,12 @@ class PSA_Post_Type_Meta { wp_nonce_field( 'psa_save_meta', 'psa_meta_nonce' ); $textarea_fields = array( - 'psa_sequence', 'psa_mechanism', 'psa_research_apps', - 'psa_safety_profile', 'psa_dosage_info', 'psa_references', + 'psa_sequence', + 'psa_mechanism', + 'psa_research_apps', + 'psa_safety_profile', + 'psa_dosage_info', + 'psa_references', ); echo ''; @@ -60,8 +64,11 @@ class PSA_Post_Type_Meta { if ( 'psa_source' === $key ) { $options = array( - '' => '— Select —', 'ai-generated' => 'AI Generated', - 'pubchem' => 'PubChem Verified', 'manual' => 'Manual Entry', 'pending' => 'Pending Generation', + '' => '— Select —', + 'ai-generated' => 'AI Generated', + 'pubchem' => 'PubChem Verified', + 'manual' => 'Manual Entry', + 'pending' => 'Pending Generation', ); echo '