feat: interactive model picker with searchable popup and pricing

Replace plain text inputs for analysis/writing/editor model fields
with a rich model picker that fetches the OpenRouter model registry
and displays a searchable popup with model name, provider, input/output
pricing per 1M tokens, and context window. Also extracts source status
and model field renderers into dedicated field classes to keep
class-admin-page.php under the 300-line cap.

New files:
- assets/js/model-picker.js — popup UI with search, filtering, caching
- assets/css/model-picker.css — popup styles matching admin design system
- includes/admin/fields/class-open-router-model-field.php — server render
- includes/admin/fields/class-source-status-field.php — extracted field

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
peptiderepo
2026-04-18 02:53:40 +07:00
parent dcc8b7b183
commit 31f303a831
9 changed files with 670 additions and 51 deletions

View File

@@ -65,13 +65,16 @@ class PRAutoBlogger_Admin_Page {
}
wp_enqueue_style( 'prautoblogger-admin', PRAUTOBLOGGER_PLUGIN_URL . 'assets/css/admin.css', [], PRAUTOBLOGGER_VERSION );
wp_enqueue_style( 'prautoblogger-model-picker', PRAUTOBLOGGER_PLUGIN_URL . 'assets/css/model-picker.css', [ 'prautoblogger-admin' ], PRAUTOBLOGGER_VERSION );
wp_enqueue_script( 'prautoblogger-admin', PRAUTOBLOGGER_PLUGIN_URL . 'assets/js/admin.js', [ 'jquery' ], PRAUTOBLOGGER_VERSION, true );
wp_enqueue_script( 'prautoblogger-model-picker', PRAUTOBLOGGER_PLUGIN_URL . 'assets/js/model-picker.js', [ 'jquery', 'prautoblogger-admin' ], PRAUTOBLOGGER_VERSION, true );
wp_localize_script( 'prautoblogger-admin', 'prautobloggerAdmin', [
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'generateNonce' => wp_create_nonce( 'prautoblogger_generate_now' ),
'imageNonce' => wp_create_nonce( 'prautoblogger_generate_image' ),
'testNonce' => wp_create_nonce( 'prautoblogger_test_connection' ),
'modelsNonce' => wp_create_nonce( 'prautoblogger_get_models' ),
'reviewNonce' => wp_create_nonce( 'prautoblogger_review_queue' ),
'generatingText' => __( 'Generating...', 'prautoblogger' ),
'generateText' => __( 'Generate Now', 'prautoblogger' ),
@@ -152,6 +155,10 @@ class PRAutoBlogger_Admin_Page {
}
break;
case 'model_select':
$this->render_model_select_field( $id, (string) $value, $args );
break;
case 'source_status':
$this->render_source_status_field();
break;
@@ -174,58 +181,25 @@ class PRAutoBlogger_Admin_Page {
}
/**
* Render the Reddit source status indicator.
* Delegate source status rendering to the dedicated field class.
*
* Shows Reddit RSS (primary) and .json (fallback) availability
* with live status checks via transient cache and rate limit info.
*
* Side effects: Reads transients for cached availability status.
*
* @return void
* @see admin/fields/class-source-status-field.php
*/
private function render_source_status_field(): void {
$last_collection = get_option( 'prautoblogger_last_collection_time', '' );
$last_source_used = get_option( 'prautoblogger_last_source_used', '' );
PRAutoBlogger_Source_Status_Field::render();
}
echo '<div class="ab-source-status">';
// Reddit RSS — primary source.
printf(
'<div class="ab-source-row">'
. '<span class="ab-source-dot ab-status-ok"></span>'
. '<strong>%s</strong> <span class="ab-source-badge">%s</span>'
. '<span class="ab-source-label">%s</span>'
. '</div>',
esc_html__( 'Reddit RSS', 'prautoblogger' ),
esc_html__( 'Primary', 'prautoblogger' ),
esc_html__( 'Reliable from all IPs', 'prautoblogger' )
);
// Reddit .json — fallback source.
printf(
'<div class="ab-source-row">'
. '<span class="ab-source-dot ab-status-ok"></span>'
. '<strong>%s</strong> <span class="ab-source-badge ab-badge-secondary">%s</span>'
. '<span class="ab-source-label">%s</span>'
. '</div>',
esc_html__( 'Reddit .json', 'prautoblogger' ),
esc_html__( 'Fallback + Comments', 'prautoblogger' ),
esc_html__( 'May be IP-blocked on some hosts', 'prautoblogger' )
);
// Last collection info.
if ( '' !== $last_collection ) {
$time_ago = human_time_diff( (int) $last_collection, time() );
printf(
'<div class="ab-source-meta">%s <strong>%s</strong> %s</div>',
esc_html__( 'Last collection:', 'prautoblogger' ),
/* translators: %s is a human-readable time difference like "2 hours" */
esc_html( sprintf( __( '%s ago', 'prautoblogger' ), $time_ago ) ),
'' !== $last_source_used ? esc_html( sprintf( __( 'via %s', 'prautoblogger' ), $last_source_used ) ) : ''
);
}
echo '</div>';
/**
* Delegate model picker rendering to the dedicated field class.
*
* @param string $id Field HTML id/name.
* @param string $value Current model id.
* @param array<string, mixed> $args Field definition.
*
* @see admin/fields/class-openrouter-model-field.php
*/
private function render_model_select_field( string $id, string $value, array $args ): void {
PRAutoBlogger_OpenRouter_Model_Field::render( $id, $value, $args );
}
/**