v0.11.0 — OpenRouter model picker (#111)

v0.11.0 — OpenRouter model picker (AJAX refresh + cost preview + Phase-2 asset extract)

Replaces the free-text slug inputs for analysis_model, writing_model, and editor_model with a smart picker fed by a daily-refreshed OpenRouter model registry. Picker shows context size + input/output price + provider, filters by per-field capability, previews per-generation cost using existing cost-tracker history, and keeps a custom-slug fallback. Daily registry refresh fires from inside on_daily_generation() before lock acquire — no new top-level cron event.

Commit sequence (preserved for review):
- 353aa4e  feat(models): OpenRouter model picker + AJAX refresh + cost preview
- 283fa55  fix(phpcs): resolve 4 PHPCS violations in model-registry-refresh handler
- 1b1336a  style(phpcs): fix array syntax in model field + cost tracker
- 283f9f9  style(phpcs): fix tabs vs spaces in settings-page.php
- aa78591  refactor(assets): extract model-picker refresh handler to generic-handle file

Verification
- PHPCS strict (CI scope): 0 errors / 0 warnings
- PHPUnit: 206/206 (555 assertions, 2 pre-existing skips)
- All new files under 300-line cap (field renderer 163, AJAX handler 82)
- Security: AJAX nonce + manage_options cap; output escaped; input sanitized
- Phase 2 ready: zero PRAUTOBLOGGER_* coupling in registry/picker/AJAX classes; generic asset handle peptiderepo-model-picker

QA verdict: PASS (qa-reviews/prautoblogger/2026-04-23-aa78591.md)

Out of scope (untouched): cto-review.yml, prautoblogger_image_model field (Workers AI FLUX, Phase 3), Peptide Search AI / Peptide News fields (Phase 2).

Co-authored-by: Engineer PRAutoBlogger (in-session subagent) <engineer-prautoblogger@peptiderepo.users.noreply.github.com>
This commit is contained in:
peptiderepo
2026-04-23 12:11:59 +07:00
committed by GitHub
parent e3e867353a
commit 7401addbd9
14 changed files with 373 additions and 11 deletions

22
templates/admin/settings-page.php Executable file → Normal file
View File

@@ -102,14 +102,20 @@ $next_run = wp_next_scheduled( 'prautoblogger_daily_generation' );
<?php foreach ( $sections as $sid => $sec ) : ?>
<div class="ab-panel <?php echo $active_tab === $sid ? 'ab-panel-active' : ''; ?>"
data-tab="<?php echo esc_attr( $sid ); ?>"
role="tabpanel">
<div class="ab-panel-head">
<h2 class="ab-panel-title"><?php echo esc_html( $sec['title'] ); ?></h2>
<?php if ( ! empty( $sec['description'] ) ) : ?>
<p class="ab-panel-desc"><?php echo esc_html( $sec['description'] ); ?></p>
<?php endif; ?>
</div>
data-tab="<?php echo esc_attr( $sid ); ?>"
role="tabpanel">
<div class="ab-panel-head">
<h2 class="ab-panel-title"><?php echo esc_html( $sec['title'] ); ?></h2>
<?php if ( ! empty( $sec['description'] ) ) : ?>
<p class="ab-panel-desc"><?php echo esc_html( $sec['description'] ); ?></p>
<?php endif; ?>
<?php if ( 'prautoblogger_models' === $sid ) : ?>
<button type="button" id="prautoblogger-refresh-models" class="ab-btn ab-btn-secondary ab-btn-sm" data-nonce="<?php echo esc_attr( wp_create_nonce( 'prautoblogger_refresh_models' ) ); ?>">
<span class="dashicons dashicons-update"></span>
<span class="ab-btn-label"><?php esc_html_e( 'Refresh Model List', 'prautoblogger' ); ?></span>
</button>
<?php endif; ?>
</div>
<table class="form-table ab-form-table" role="presentation">
<?php do_settings_fields( 'prautoblogger-settings', $sid ); ?>
</table>