fix(ci): phpcbf auto-format + add missing bootstrap requires for tests

212 PHPCS formatting errors auto-fixed by phpcbf across 18 files.
All remaining issues are warnings (exit 2), not errors — CI treats
exit 2 as success via the same pattern as peptide-news-plugin.

Also adds class-psa-ai-content.php and class-psa-directory-api.php to
tests/bootstrap.php require list — both files exist in includes/ but
were missing from bootstrap, causing 5 PHPUnit "Class not found"
failures in AIGeneratorTest and DirectoryTest.
This commit is contained in:
peptiderepo
2026-04-24 06:33:41 +00:00
parent 4ac81f5d23
commit 3dda80b771
19 changed files with 436 additions and 255 deletions

View File

@@ -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 {
<h1><?php esc_html_e( 'Peptide Search AI Settings', 'peptide-search-ai' ); ?></h1>
<form method="post" action="options.php">
<?php settings_fields( 'psa_settings_group' ); do_settings_sections( 'peptide-search-ai' ); submit_button(); ?>
<?php
settings_fields( 'psa_settings_group' );
do_settings_sections( 'peptide-search-ai' );
submit_button();
?>
</form>
<hr />
@@ -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 );
?>
<div style="background:#f5f5f5;padding:15px;border-radius:4px;margin-bottom:20px;">
<h3><?php esc_html_e( 'This Month', 'peptide-search-ai' ); ?></h3>
@@ -216,6 +224,7 @@ class PSA_Admin_Page {
<?php endforeach; ?>
</tbody>
</table>
<?php endif;
<?php
endif;
}
}

View File

@@ -40,9 +40,13 @@ class PSA_Admin {
/** Register settings fields — rendering delegated to PSA_Admin_Page. */
public static function register_settings(): void {
register_setting( 'psa_settings_group', 'psa_settings', array(
'sanitize_callback' => 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 '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Category migration completed.', 'peptide-search-ai' ) . '</p></div>';
} );
add_action(
'admin_notices',
function () {
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Category migration completed.', 'peptide-search-ai' ) . '</p></div>';
}
);
}
if ( 'reenrich' === $action && wp_verify_nonce( $nonce, 'psa_reenrich' ) ) {
$queued = self::queue_reenrichment();
add_action( 'admin_notices', function () use ( $queued ) {
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html( sprintf(
__( '%d peptides queued for re-enrichment.', 'peptide-search-ai' ), $queued
) ) . '</p></div>';
} );
add_action(
'admin_notices',
function () use ( $queued ) {
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html(
sprintf(
__( '%d peptides queued for re-enrichment.', 'peptide-search-ai' ),
$queued
)
) . '</p></div>';
}
);
}
}
@@ -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(); }
}

View File

@@ -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 ); }
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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(

View File

@@ -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 ); }
}

View File

@@ -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 );
}

View File

@@ -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 ); }
}

View File

@@ -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.

View File

@@ -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 .= '<h2>Research &amp; Potential Benefits</h2>' . "\n";
$c .= '<p><em>The following potential benefits are based on animal studies and preclinical research. Human clinical trial data is extremely limited.</em></p>' . "\n";
$c .= '<h2>Research &amp; Potential Benefits</h2>' . "\n";
$c .= '<p><em>The following potential benefits are based on animal studies and preclinical research. Human clinical trial data is extremely limited.</em></p>' . "\n";
$research = $ai_data['research_benefits'] ?? $ai_data['research_applications'] ?? '';
$c .= self::research_with_subheadings( $research );
$c .= self::research_with_subheadings( $research );
}
// Administration & Dosing.

View File

@@ -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,
)
);

View File

@@ -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 '<table class="form-table">';
@@ -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 '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $key ) . '">';
foreach ( $options as $opt_val => $opt_label ) {
@@ -113,7 +120,7 @@ class PSA_Post_Type_Meta {
public static function save_meta( int $post_id ): void {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
if ( ! isset( $_POST['psa_meta_nonce'] ) ||
! wp_verify_nonce( wp_unslash( $_POST['psa_meta_nonce'] ), 'psa_save_meta' ) ) {
! wp_verify_nonce( wp_unslash( $_POST['psa_meta_nonce'] ), 'psa_save_meta' ) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {

View File

@@ -68,15 +68,15 @@ class PSA_Post_Type {
/** Extended meta fields added in v4.3.0 for the directory feature. */
const EXTENDED_META_FIELDS = array(
'psa_half_life' => 'Half-Life',
'psa_stability' => 'Stability',
'psa_solubility' => 'Solubility / Reconstitution',
'psa_vial_size_mg' => 'Common Vial Size (mg)',
'psa_storage_lyophilized' => 'Storage (Lyophilized)',
'psa_storage_reconstituted' => 'Storage (Reconstituted)',
'psa_typical_dose_mcg' => 'Typical Research Dose',
'psa_cycle_parameters' => 'Cycle / Protocol Parameters',
'psa_amino_acid_count' => 'Amino Acid Count',
'psa_half_life' => 'Half-Life',
'psa_stability' => 'Stability',
'psa_solubility' => 'Solubility / Reconstitution',
'psa_vial_size_mg' => 'Common Vial Size (mg)',
'psa_storage_lyophilized' => 'Storage (Lyophilized)',
'psa_storage_reconstituted' => 'Storage (Reconstituted)',
'psa_typical_dose_mcg' => 'Typical Research Dose',
'psa_cycle_parameters' => 'Cycle / Protocol Parameters',
'psa_amino_acid_count' => 'Amino Acid Count',
);
/**
@@ -121,8 +121,11 @@ class PSA_Post_Type {
if ( 'psa_source' === $column ) {
$source = get_post_meta( $post_id, 'psa_source', true );
$labels = array(
'ai-generated' => 'AI', 'pubchem' => 'PubChem',
'manual' => 'Manual', 'pending' => 'Pending', 'failed' => 'Failed',
'ai-generated' => 'AI',
'pubchem' => 'PubChem',
'manual' => 'Manual',
'pending' => 'Pending',
'failed' => 'Failed',
);
echo esc_html( $labels[ $source ] ?? '—' );
}
@@ -140,14 +143,18 @@ class PSA_Post_Type {
// ── Backward-compatible proxies (meta boxes moved to PSA_Post_Type_Meta) ──
/** @see PSA_Post_Type_Meta::add_meta_boxes() */
public static function add_meta_boxes(): void { PSA_Post_Type_Meta::add_meta_boxes(); }
public static function add_meta_boxes(): void {
PSA_Post_Type_Meta::add_meta_boxes(); }
/** @see PSA_Post_Type_Meta::render_meta_box() */
public static function render_meta_box( $post ): void { PSA_Post_Type_Meta::render_meta_box( $post ); }
public static function render_meta_box( $post ): void {
PSA_Post_Type_Meta::render_meta_box( $post ); }
/** @see PSA_Post_Type_Meta::render_extended_meta_box() */
public static function render_extended_meta_box( $post ): void { PSA_Post_Type_Meta::render_extended_meta_box( $post ); }
public static function render_extended_meta_box( $post ): void {
PSA_Post_Type_Meta::render_extended_meta_box( $post ); }
/** @see PSA_Post_Type_Meta::save_meta() */
public static function save_meta( int $post_id ): void { PSA_Post_Type_Meta::save_meta( $post_id ); }
public static function save_meta( int $post_id ): void {
PSA_Post_Type_Meta::save_meta( $post_id ); }
}

View File

@@ -40,7 +40,12 @@ class PSA_Search_Handler {
$results = PSA_Search::search_peptides( $query );
if ( ! empty( $results['results'] ) ) {
wp_send_json_success( array( 'status' => 'found', 'data' => $results ) );
wp_send_json_success(
array(
'status' => 'found',
'data' => $results,
)
);
return;
}
@@ -53,7 +58,12 @@ class PSA_Search_Handler {
// 3. Check rate limit before attempting new generation.
if ( ! self::check_rate_limit() ) {
wp_send_json_success( array( 'status' => 'rate_limited', 'message' => 'Too many requests. Please try again later.' ) );
wp_send_json_success(
array(
'status' => 'rate_limited',
'message' => 'Too many requests. Please try again later.',
)
);
return;
}
@@ -78,10 +88,12 @@ class PSA_Search_Handler {
error_log( 'PSA: Max retries (' . PSA_Config::MAX_GENERATION_RETRIES . ') exceeded for "' . $pending->post_title . '" (post ' . $pending->ID . '). Marking as failed.' );
update_post_meta( $pending->ID, 'psa_source', 'failed' );
update_post_meta( $pending->ID, 'psa_generation_error', 'Generation failed after ' . PSA_Config::MAX_GENERATION_RETRIES . ' attempts.' );
wp_send_json_success( array(
'status' => 'invalid',
'message' => 'We were unable to generate content for this peptide. Please try again later.',
) );
wp_send_json_success(
array(
'status' => 'invalid',
'message' => 'We were unable to generate content for this peptide. Please try again later.',
)
);
return;
}
@@ -91,7 +103,12 @@ class PSA_Search_Handler {
self::schedule_background_generation( $pending->ID, $pending->post_title );
}
wp_send_json_success( array( 'status' => 'pending', 'peptide_name' => $pending->post_title ) );
wp_send_json_success(
array(
'status' => 'pending',
'peptide_name' => $pending->post_title,
)
);
}
/**
@@ -124,10 +141,12 @@ class PSA_Search_Handler {
$daily_key = 'psa_daily_gen_' . gmdate( 'Y-m-d' );
$daily_count = (int) get_transient( $daily_key );
if ( $daily_count >= PSA_Config::DAILY_GENERATION_CAP ) {
wp_send_json_success( array(
'status' => 'rate_limited',
'message' => 'Our system has reached its daily limit for adding new peptides. Please try again tomorrow.',
) );
wp_send_json_success(
array(
'status' => 'rate_limited',
'message' => 'Our system has reached its daily limit for adding new peptides. Please try again tomorrow.',
)
);
return;
}
@@ -135,14 +154,21 @@ class PSA_Search_Handler {
if ( is_wp_error( $validation ) ) {
error_log( 'PSA: Validation error for "' . $query . '": ' . $validation->get_error_message() );
wp_send_json_success( array( 'status' => 'invalid', 'message' => 'Could not verify this peptide name.' ) );
wp_send_json_success(
array(
'status' => 'invalid',
'message' => 'Could not verify this peptide name.',
)
);
}
if ( empty( $validation['is_valid'] ) ) {
wp_send_json_success( array(
'status' => 'invalid',
'message' => $validation['reason'] ?? 'This does not appear to be a recognized peptide.',
) );
wp_send_json_success(
array(
'status' => 'invalid',
'message' => $validation['reason'] ?? 'This does not appear to be a recognized peptide.',
)
);
}
// Valid peptide — increment rate limit and create placeholder.
@@ -165,7 +191,12 @@ class PSA_Search_Handler {
error_log( 'PSA: Scheduling async generation for "' . $canonical_name . '" (post ' . $placeholder_id . ')' );
self::schedule_background_generation( $placeholder_id, $canonical_name );
wp_send_json_success( array( 'status' => 'pending', 'peptide_name' => $canonical_name ) );
wp_send_json_success(
array(
'status' => 'pending',
'peptide_name' => $canonical_name,
)
);
}
/**

View File

@@ -134,7 +134,7 @@ class PSA_Search {
* @return string HTML output.
*/
public static function render_search_form( $atts ): string {
self::$instance_count++;
++self::$instance_count;
$instance = self::$instance_count;
$atts = shortcode_atts(
@@ -256,7 +256,11 @@ class PSA_Search {
$results[] = self::format_peptide( (int) $id );
}
$response = array( 'results' => $results, 'total' => count( $results ), 'query' => $query );
$response = array(
'results' => $results,
'total' => count( $results ),
'query' => $query,
);
set_transient( $cache_key, $response, 6 * HOUR_IN_SECONDS );
@@ -290,11 +294,14 @@ class PSA_Search {
// ── Backward-compatible proxies ─────────────────────────────────────
/** @see PSA_Search_Handler::ajax_search() */
public static function ajax_search(): void { PSA_Search_Handler::ajax_search(); }
public static function ajax_search(): void {
PSA_Search_Handler::ajax_search(); }
/** @see PSA_Search_Handler::create_pending_placeholder() */
public static function create_pending_placeholder( string $peptide_name ) { return PSA_Search_Handler::create_pending_placeholder( $peptide_name ); }
public static function create_pending_placeholder( string $peptide_name ) {
return PSA_Search_Handler::create_pending_placeholder( $peptide_name ); }
/** @see PSA_Search_Handler::schedule_background_generation() */
public static function schedule_background_generation( int $post_id, string $peptide_name ): void { PSA_Search_Handler::schedule_background_generation( $post_id, $peptide_name ); }
public static function schedule_background_generation( int $post_id, string $peptide_name ): void {
PSA_Search_Handler::schedule_background_generation( $post_id, $peptide_name ); }
}

View File

@@ -210,7 +210,7 @@ class PSA_Template {
'class' => 'psa-badge-manual',
),
);
$badge = $badges[ $source ] ?? $badges['ai-generated'];
$badge = $badges[ $source ] ?? $badges['ai-generated'];
$html = '<div class="psa-source-badge ' . esc_attr( $badge['class'] ) . '">';
$html .= esc_html( $badge['label'] );

View File

@@ -156,20 +156,20 @@ function psa_enqueue_frontend_assets() {
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'psa_search_nonce' ),
'i18n' => array(
'error' => __( 'Error:', 'peptide-search-ai' ),
'searchFailed' => __( 'Search failed.', 'peptide-search-ai' ),
'unexpected' => __( 'Unexpected response.', 'peptide-search-ai' ),
'rateLimited' => __( 'Too many requests. Please try again later.', 'peptide-search-ai' ),
'networkError' => __( 'Could not connect to the server. Please try again.', 'peptide-search-ai' ),
'timeout' => __( 'Request timed out. The server took too long to respond. Please try again.', 'peptide-search-ai' ),
'connectionError' => __( 'Network error. Please check your connection and try again.', 'peptide-search-ai' ),
'serverError' => __( 'Server error. Please try again later.', 'peptide-search-ai' ),
'unavailable' => __( 'Server is temporarily unavailable. Please try again later.', 'peptide-search-ai' ),
'peptidesFound' => __( 'peptide(s) found', 'peptide-search-ai' ),
'pendingMsg' => __( 'is currently being added to our database. Please check back again later.', 'peptide-search-ai' ),
'invalidMsg' => __( 'does not appear to be a recognized peptide. Please check the spelling or try a different search term.', 'peptide-search-ai' ),
'verified' => __( 'Verified', 'peptide-search-ai' ),
'curated' => __( 'Curated', 'peptide-search-ai' ),
'error' => __( 'Error:', 'peptide-search-ai' ),
'searchFailed' => __( 'Search failed.', 'peptide-search-ai' ),
'unexpected' => __( 'Unexpected response.', 'peptide-search-ai' ),
'rateLimited' => __( 'Too many requests. Please try again later.', 'peptide-search-ai' ),
'networkError' => __( 'Could not connect to the server. Please try again.', 'peptide-search-ai' ),
'timeout' => __( 'Request timed out. The server took too long to respond. Please try again.', 'peptide-search-ai' ),
'connectionError' => __( 'Network error. Please check your connection and try again.', 'peptide-search-ai' ),
'serverError' => __( 'Server error. Please try again later.', 'peptide-search-ai' ),
'unavailable' => __( 'Server is temporarily unavailable. Please try again later.', 'peptide-search-ai' ),
'peptidesFound' => __( 'peptide(s) found', 'peptide-search-ai' ),
'pendingMsg' => __( 'is currently being added to our database. Please check back again later.', 'peptide-search-ai' ),
'invalidMsg' => __( 'does not appear to be a recognized peptide. Please check the spelling or try a different search term.', 'peptide-search-ai' ),
'verified' => __( 'Verified', 'peptide-search-ai' ),
'curated' => __( 'Curated', 'peptide-search-ai' ),
),
)
);

View File

@@ -129,5 +129,7 @@ require_once ABSPATH . 'includes/class-psa-config.php';
require_once ABSPATH . 'includes/class-psa-cost-tracker.php';
require_once ABSPATH . 'includes/class-psa-openrouter.php';
require_once ABSPATH . 'includes/class-psa-kb-builder.php';
require_once ABSPATH . 'includes/class-psa-ai-content.php';
require_once ABSPATH . 'includes/class-psa-directory-api.php';
require_once ABSPATH . 'includes/class-psa-ai-generator.php';
require_once ABSPATH . 'includes/class-psa-upgrade.php';