render_styles();
echo '
';
echo '
' . esc_html__( 'PR Vision — Settings', 'pr-vision' ) . '
';
$this->render_notices();
echo '
' . esc_html__( 'Model Manager', 'pr-vision' ) . '
';
( new PRV_Model_Manager_Table() )->render();
echo '';
$this->render_config_form();
$this->render_save_bar();
$this->render_scripts();
echo '
';
}
/**
* Render dark CSS variables and component styles.
*
* @return void
*/
private function render_styles(): void {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '';
}
/**
* Render POST-redirect notices (saved, run done, model CRUD, lock).
*
* @return void
*/
private function render_notices(): void {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( ! empty( $_GET['prv_saved'] ) ) {
echo '' . esc_html__( 'Settings saved.', 'pr-vision' ) . '
';
}
if ( ! empty( $_GET['prv_run_done'] ) ) {
$p = absint( $_GET['prv_probed'] ?? 0 );
$s = absint( $_GET['prv_skipped'] ?? 0 );
$trn = ! empty( $_GET['prv_truncated'] );
$msg = sprintf( 'Run complete: %d probed, %d skipped.%s', $p, $s, $trn ? ' Last run was truncated by the monthly budget cap.' : '' );
echo '' . esc_html( $msg ) . '
';
}
if ( ! empty( $_GET['prv_run_locked'] ) ) {
echo '' . esc_html__( 'A run is already in progress — try again when it finishes.', 'pr-vision' ) . '
';
}
if ( ! empty( $_GET['prv_model_added'] ) ) {
echo '' . esc_html__( 'Model added.', 'pr-vision' ) . '
';
}
if ( ! empty( $_GET['prv_model_updated'] ) ) {
echo '' . esc_html__( 'Model updated.', 'pr-vision' ) . '
';
}
if ( ! empty( $_GET['prv_model_removed'] ) ) {
echo '' . esc_html__( 'Model removed.', 'pr-vision' ) . '
';
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
}
/**
* Render the secondary config form.
*
* @return void
*/
private function render_config_form(): void {
echo '';
}
/**
* Render cadence selector with next-run timestamp.
*
* @return void
*/
private function render_cadence_field(): void {
$cadence = PRV_Config::get_cadence();
$next_ts = PRV_Cron::next_run_timestamp();
$next_str = $next_ts ? gmdate( 'Y-m-d H:i T', (int) $next_ts ) : 'Not scheduled';
echo '';
echo '
';
echo '
' . esc_html__( 'Next run: ', 'pr-vision' ) . esc_html( $next_str ) . '
';
}
/**
* Render monthly budget cap input.
*
* @return void
*/
private function render_budget_field(): void {
$budget = PRV_Config::get_monthly_budget_usd();
echo '';
echo '
';
}
/**
* Render live projected-cost inset panel.
*
* @return void
*/
private function render_projected_cost(): void {
$cost = PRV_Config::get_projected_cost();
$cls = $cost['over_cap'] ? 'prv-cost-inset over-cap' : 'prv-cost-inset';
echo '';
echo 'Projected cost: ~$' . esc_html( number_format( $cost['per_run_usd'], 4 ) ) . '/run · ';
echo '~$' . esc_html( number_format( $cost['per_month_usd'], 4 ) ) . '/month · ';
echo esc_html( (string) $cost['probe_count'] ) . ' probes/run';
if ( $cost['over_cap'] ) {
echo ' ⚠ Over cap — runs will truncate';
}
echo '
';
}
/**
* Render peptide set textarea (JSON).
*
* @return void
*/
private function render_peptides_field(): void {
$peptides = PRV_Config::get_peptides();
echo '';
echo '
' . esc_html( count( $peptides ) ) . ' peptides (JSON array of {slug, label}).
';
echo '
';
}
/**
* Render prompt intents textarea.
*
* @return void
*/
private function render_intents_field(): void {
$intents = PRV_Config::get_prompt_intents();
echo '';
echo '
';
}
/**
* Render the read-only API-key three-state status.
*
* @return void
*/
private function render_api_key_status(): void {
$defined = defined( 'PRV_OPENROUTER_API_KEY' ) && '' !== constant( 'PRV_OPENROUTER_API_KEY' );
$status = (string) get_option( 'prv_api_key_status', 'unknown' );
echo '' . esc_html__( 'API Key Status (read-only)', 'pr-vision' ) . '
';
if ( ! $defined ) {
echo '
● ' . esc_html__( 'Not defined — set PRV_OPENROUTER_API_KEY in wp-config.php', 'pr-vision' ) . '';
} elseif ( 'failed' === $status ) {
echo '
● ' . esc_html__( 'Defined — last run failed (key may be invalid or revoked)', 'pr-vision' ) . '';
} else {
echo '
● ' . esc_html__( 'Defined — last run OK', 'pr-vision' ) . '';
}
echo '
';
}
/**
* Render the sticky save-bar with scoring-change warning and Run now.
*
* @return void
*/
private function render_save_bar(): void {
echo '';
echo '';
echo '';
echo '';
$next_ts = PRV_Cron::next_run_timestamp();
if ( $next_ts ) {
echo '' . esc_html__( 'Next auto-run: ', 'pr-vision' ) . esc_html( gmdate( 'Y-m-d H:i T', (int) $next_ts ) ) . '';
}
echo '
';
}
/**
* Render inline JS: scoring-change warn, sort headers, Test AJAX, Run-now guard.
*
* @return void
*/
private function render_scripts(): void {
$ajax_url = admin_url( 'admin-ajax.php' );
$nonce = wp_create_nonce( PRV_Settings_Page::NONCE_TEST );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '';
}
}