$args Field definition from settings fields. */ public static function render( string $id, string $value, array $args ): void { $capability = $args['capability'] ?? 'text→text'; $display_name = '' !== $value ? $value : __( '— Select a model —', 'prautoblogger' ); $display_price = ''; if ( 'image_generation' === $capability ) { // Resolve from the static image model list defined in settings. $image_models = PRAutoBlogger_Settings_Fields_Extended::get_image_models(); foreach ( $image_models as $im ) { if ( ( $im['id'] ?? '' ) === $value ) { $display_name = $im['name'] ?? $value; $display_price = '$' . number_format( (float) ( $im['cost_per_image'] ?? 0 ), 4 ) . '/image'; break; } } } else { // Resolve from the OpenRouter model registry cache. $registry = prautoblogger()->get_executor()->get_model_registry(); $model = $registry->find_model( $value ); if ( null !== $model ) { $display_name = $model['name'] ?? $value; $in = (float) ( $model['input_price_per_m'] ?? 0 ); $out = (float) ( $model['output_price_per_m'] ?? 0 ); $display_price = self::format_price( $in ) . ' / ' . self::format_price( $out ); } } // Hidden input carries the actual value for the form submission. printf( '', esc_attr( $id ), esc_attr( $id ), esc_attr( $value ) ); // Trigger button — opens the model picker popup via JS. printf( '', esc_attr( $id ), esc_attr( $capability ), esc_html( $display_name ), esc_html( $display_price ) ); // Cost preview panel — only for text→text models (not image). if ( 'image_generation' !== $capability && '' !== $value ) { $cost_preview = self::get_cost_preview( $id, $value ); if ( $cost_preview ) { printf( '
%s
', wp_kses_post( $cost_preview ) ); } } } /** * Calculate and render the estimated cost preview for a model + setting combo. * * @param string $field_id Setting ID (e.g. 'prautoblogger_writing_model'). * @param string $model_id Model ID (e.g. 'anthropic/claude-3.5-haiku'). * * @return string HTML snippet for the cost preview, or empty if no history. */ private static function get_cost_preview( string $field_id, string $model_id ): string { $stages = self::get_stages_for_setting( $field_id ); if ( empty( $stages ) ) { return ''; } $registry = prautoblogger()->get_executor()->get_model_registry(); $model = $registry->find_model( $model_id ); if ( null === $model ) { return ''; } $tracker = new PRAutoBlogger_Cost_Tracker(); $tokens = $tracker->get_avg_tokens_for_stages( $stages, 30 ); if ( 0 === $tokens['sample_size'] ) { return '' . esc_html__( 'Estimated cost: — (insufficient history)', 'prautoblogger' ) . ''; } $in_price = (float) ( $model['input_price_per_m'] ?? 0 ); $out_price = (float) ( $model['output_price_per_m'] ?? 0 ); $avg_tokens_in = $tokens['avg_prompt_tokens']; $avg_tokens_out = $tokens['avg_completion_tokens']; // Cost per generation = (avg_in_tokens × in_price + avg_out_tokens × out_price) / 1_000_000 $cost_per_gen = ( ( $avg_tokens_in * $in_price ) + ( $avg_tokens_out * $out_price ) ) / 1_000_000; return sprintf( '%s %s', esc_html__( 'Estimated cost per generation:', 'prautoblogger' ), esc_html( '$' . number_format( $cost_per_gen, 4 ) ) ); } /** * Map a setting ID to its constituent pipeline stages. * * @param string $field_id Setting ID. * * @return string[] Stage names, e.g. ['outline', 'draft', 'polish'] for writing model. */ private static function get_stages_for_setting( string $field_id ): array { $map = [ 'prautoblogger_analysis_model' => [ 'analysis' ], 'prautoblogger_writing_model' => [ 'outline', 'draft', 'polish' ], 'prautoblogger_editor_model' => [ 'review' ], ]; return $map[ $field_id ] ?? []; } /** * Format a price-per-million-tokens value for display. * * @param float $price Price per 1M tokens. * @return string Formatted price string like "$0.25" or "Free". */ private static function format_price( float $price ): string { if ( $price <= 0 ) { return __( 'Free', 'prautoblogger' ); } if ( $price < 0.01 ) { return '$' . number_format( $price, 4 ); } return '$' . number_format( $price, 2 ); } }