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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(); }
|
||||
}
|
||||
|
||||
@@ -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 ); }
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 ); }
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -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 ); }
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 & 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 & 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.
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -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 ) {
|
||||
|
||||
@@ -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 ); }
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 ); }
|
||||
}
|
||||
|
||||
@@ -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'] );
|
||||
|
||||
@@ -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' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user