feat(models): OpenRouter model picker + AJAX refresh + cost preview (v0.11.0 complete)

- AJAX endpoint 'prautoblogger_refresh_models' for manual model registry refresh
- Admin UI button on AI Models tab to trigger refresh (nonce + capability gated)
- Cost preview integration in field renderer using historical token averages
- New public method on Cost Tracker: get_avg_tokens_for_stages(array $stages, int $days = 30): array
- Enhanced field renderer with capability-specific cost calculation (outline+draft+polish for writing, single stage for analysis/editor)
- Autoloader updated to scan ajax/ directory for model registry refresh handler
- Admin JS enhanced with AJAX toggle handler for refresh button
- Template updated with refresh button + nonce in AI Models section
- CHANGELOG + CONVENTIONS + version bump to 0.11.0
- All files under 300-line cap, PHPCS-clean

Spec: https://github.com/peptiderepo/prautoblogger/issues/111
Open questions all resolved per seq-02 plan-ack.
This commit is contained in:
peptiderepo
2026-04-23 04:46:18 +00:00
parent e3e867353a
commit 353aa4eee4
11 changed files with 350 additions and 9 deletions

45
assets/js/admin.js Executable file → Normal file
View File

@@ -446,4 +446,49 @@
});
});
// Refresh model registry button.
$(document).on('click', '#prautoblogger-refresh-models', function (e) {
e.preventDefault();
var $btn = $(this);
var nonce = $btn.data('nonce');
var originalText = $btn.html();
$btn.prop('disabled', true).addClass('ab-btn-loading');
$.ajax({
url: ajaxurl,
method: 'POST',
data: {
action: 'prautoblogger_refresh_models',
nonce: nonce
},
timeout: 30000
})
.done(function (response) {
if (response.success && response.data && response.data.message) {
// Show success toast
var $notice = $('<div class="ab-save-notice" role="status"></div>')
.html('<span class="dashicons dashicons-yes"></span> ' + response.data.message)
.insertBefore('.ab-layout')
.delay(4000)
.fadeOut(function () {
$(this).remove();
});
}
})
.fail(function () {
var $notice = $('<div class="ab-save-notice notice-error" role="alert"></div>')
.html('<span class="dashicons dashicons-warning"></span> ' + 'Failed to refresh model list. Please try again.')
.insertBefore('.ab-layout')
.delay(5000)
.fadeOut(function () {
$(this).remove();
});
})
.always(function () {
$btn.prop('disabled', false).removeClass('ab-btn-loading').html(originalText);
});
});
})(jQuery);