render_styles(); echo '
'; echo '

' . esc_html__( 'PR Vision — Settings', 'pr-vision' ) . '

'; ( new PRV_Costs_Renderer() )->render_subnav( 'settings' ); $this->render_notices(); echo '

' . esc_html__( 'Model Manager', 'pr-vision' ) . '

'; ( new PRV_Model_Manager_Table() )->render(); echo '
'; $this->render_config_form(); ( new PRV_Key_Manager_Renderer() )->render(); $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, key actions, 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' ) . '
'; } if ( ! empty( $_GET['prv_key_set'] ) ) { echo '
' . esc_html__( 'API key saved.', 'pr-vision' ) . '
'; } if ( ! empty( $_GET['prv_key_removed'] ) ) { echo '
' . esc_html__( 'API key removed.', 'pr-vision' ) . '
'; } if ( ! empty( $_GET['prv_key_error'] ) ) { echo '
' . esc_html__( 'Could not save API key — encryption failed. Check server logs.', 'pr-vision' ) . '
'; } // phpcs:enable WordPress.Security.NonceVerification.Recommended } /** * Render the secondary config form (cadence, budget, peptides, intents). * * @return void */ private function render_config_form(): void { echo '
'; echo ''; wp_nonce_field( PRV_Settings_Page::NONCE_SAVE, 'prv_nonce' ); echo '

' . esc_html__( 'Probe Configuration', 'pr-vision' ) . '

'; $this->render_cadence_field(); $this->render_budget_field(); $this->render_projected_cost(); $this->render_peptides_field(); $this->render_intents_field(); 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 '
'; // v0.3.0: I/O retention days. $retention = PRV_Config::get_io_retention_days(); echo '
'; echo ''; echo '' . esc_html__( 'Prompt + response text is auto-deleted after this many days. Cost and metadata are kept indefinitely.', 'pr-vision' ) . '
'; } /** * 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 sticky save-bar with scoring-change warning and Run now. * * @return void */ private function render_save_bar(): void { echo '
'; echo ''; echo ''; echo '
'; echo ''; wp_nonce_field( PRV_Settings_Page::NONCE_RUN, 'prv_nonce' ); $locked = PRV_Run_Lock::is_locked(); 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 ''; } }