diff --git a/includes/admin/class-settings-fields-extended.php b/includes/admin/class-settings-fields-extended.php index a9ace96..81621b4 100644 --- a/includes/admin/class-settings-fields-extended.php +++ b/includes/admin/class-settings-fields-extended.php @@ -23,52 +23,44 @@ class PRAutoBlogger_Settings_Fields_Extended { public static function get_image_models(): array { return [ [ - 'id' => 'black-forest-labs/flux-2-pro', - 'name' => 'FLUX.2 Pro', + 'id' => 'google/gemini-2.5-flash-image', + 'name' => 'Gemini 2.5 Flash Image (Nano Banana)', + 'provider' => 'openrouter', + 'cost_per_image' => 0.005, + 'capabilities' => [ 'image_generation' ], + 'description' => __( 'Good quality, low cost. Recommended.', 'prautoblogger' ), + ], + [ + 'id' => 'google/gemini-3.1-flash-image-preview', + 'name' => 'Gemini 3.1 Flash Image (Nano Banana 2)', + 'provider' => 'openrouter', + 'cost_per_image' => 0.008, + 'capabilities' => [ 'image_generation' ], + 'description' => __( 'Latest Google. Better quality.', 'prautoblogger' ), + ], + [ + 'id' => 'google/gemini-3-pro-image-preview', + 'name' => 'Gemini 3 Pro Image (Nano Banana Pro)', 'provider' => 'openrouter', 'cost_per_image' => 0.03, 'capabilities' => [ 'image_generation' ], - 'description' => __( 'High quality, fast. Recommended.', 'prautoblogger' ), + 'description' => __( 'Highest quality Google. Premium.', 'prautoblogger' ), ], [ - 'id' => 'black-forest-labs/flux-1.1-pro', - 'name' => 'FLUX 1.1 Pro', + 'id' => 'openai/gpt-5-image-mini', + 'name' => 'GPT-5 Image Mini', 'provider' => 'openrouter', - 'cost_per_image' => 0.04, + 'cost_per_image' => 0.02, 'capabilities' => [ 'image_generation' ], - 'description' => __( 'Previous gen Pro. Slightly pricier.', 'prautoblogger' ), + 'description' => __( 'OpenAI budget image model.', 'prautoblogger' ), ], [ - 'id' => 'black-forest-labs/flux-1.1-pro-ultra', - 'name' => 'FLUX 1.1 Pro Ultra', + 'id' => 'openai/gpt-5-image', + 'name' => 'GPT-5 Image', 'provider' => 'openrouter', - 'cost_per_image' => 0.06, + 'cost_per_image' => 0.08, 'capabilities' => [ 'image_generation' ], - 'description' => __( 'Highest quality FLUX. Premium.', 'prautoblogger' ), - ], - [ - 'id' => 'bytedance/seedream-3.0', - 'name' => 'Seedream 3.0', - 'provider' => 'openrouter', - 'cost_per_image' => 0.003, - 'capabilities' => [ 'image_generation' ], - 'description' => __( 'Budget. Good quality, very low cost.', 'prautoblogger' ), - ], - [ - 'id' => 'google/gemini-2.5-flash-preview:image', - 'name' => 'Gemini 2.5 Flash Image', - 'provider' => 'openrouter', - 'cost_per_image' => 0.01, - 'capabilities' => [ 'image_generation' ], - 'description' => __( 'Google multimodal. Text-in-image.', 'prautoblogger' ), - ], - [ - 'id' => 'openai/gpt-image-1', - 'name' => 'GPT Image 1', - 'provider' => 'openrouter', - 'cost_per_image' => 0.04, - 'capabilities' => [ 'image_generation' ], - 'description' => __( 'OpenAI. Photorealistic style.', 'prautoblogger' ), + 'description' => __( 'OpenAI premium. Photorealistic.', 'prautoblogger' ), ], [ 'id' => 'flux-1-schnell', diff --git a/includes/providers/class-open-router-image-pricing.php b/includes/providers/class-open-router-image-pricing.php index 1beb5c0..621dfc4 100644 --- a/includes/providers/class-open-router-image-pricing.php +++ b/includes/providers/class-open-router-image-pricing.php @@ -23,16 +23,15 @@ class PRAutoBlogger_OpenRouter_Image_Pricing { * @var array */ private const COST_PER_IMAGE = [ - 'black-forest-labs/flux-2-pro' => 0.03, - 'black-forest-labs/flux-1.1-pro' => 0.04, - 'black-forest-labs/flux-1.1-pro-ultra' => 0.06, - 'bytedance/seedream-3.0' => 0.003, - 'google/gemini-2.5-flash-preview:image' => 0.01, - 'openai/gpt-image-1' => 0.04, + 'google/gemini-2.5-flash-image' => 0.005, + 'google/gemini-3.1-flash-image-preview' => 0.008, + 'google/gemini-3-pro-image-preview' => 0.03, + 'openai/gpt-5-image-mini' => 0.02, + 'openai/gpt-5-image' => 0.08, ]; /** @var string Default model when none is configured. */ - public const DEFAULT_MODEL = 'black-forest-labs/flux-2-pro'; + public const DEFAULT_MODEL = 'google/gemini-2.5-flash-image'; /** * Resolve the model identifier from a hint, falling back to site option, diff --git a/includes/providers/class-open-router-image-provider.php b/includes/providers/class-open-router-image-provider.php index b85706e..cc11203 100644 --- a/includes/providers/class-open-router-image-provider.php +++ b/includes/providers/class-open-router-image-provider.php @@ -233,10 +233,12 @@ class PRAutoBlogger_OpenRouter_Image_Provider implements PRAutoBlogger_Image_Pro 'messages' => [ [ 'role' => 'user', - 'content' => $prompt, + 'content' => 'Generate an image: ' . $prompt, ], ], - 'modalities' => [ 'image' ], + // Use ["image", "text"] — some providers (Gemini) require both + // modalities even when we only want the image output. + 'modalities' => [ 'image', 'text' ], 'image_config' => [ 'aspect_ratio' => $aspect, ], diff --git a/includes/providers/class-open-router-image-support.php b/includes/providers/class-open-router-image-support.php index e611f7e..b1addbb 100644 --- a/includes/providers/class-open-router-image-support.php +++ b/includes/providers/class-open-router-image-support.php @@ -59,6 +59,16 @@ class PRAutoBlogger_OpenRouter_Image_Support { ); } + // OpenRouter routers may wrap upstream errors in an HTTP 200 body. + if ( isset( $decoded['error']['message'] ) ) { + throw new \RuntimeException( + sprintf( + esc_html__( 'OpenRouter upstream image error: %s', 'prautoblogger' ), + esc_html( $decoded['error']['message'] ) + ) + ); + } + $images = $decoded['choices'][0]['message']['images'] ?? []; if ( empty( $images ) ) { throw new \RuntimeException( diff --git a/includes/providers/class-open-router-response-parser.php b/includes/providers/class-open-router-response-parser.php index aa5947c..5e94cd3 100644 --- a/includes/providers/class-open-router-response-parser.php +++ b/includes/providers/class-open-router-response-parser.php @@ -50,6 +50,21 @@ class PRAutoBlogger_OpenRouter_Response_Parser { * @throws \RuntimeException If response structure is invalid. */ public function parse_success( array $data, string $model ): array { + // OpenRouter routers (e.g. openrouter/free) may wrap upstream provider + // errors inside an HTTP 200 body with an `error` object. Surface them + // as RuntimeExceptions so the retry loop or caller can handle them. + if ( isset( $data['error']['message'] ) ) { + $upstream_code = (int) ( $data['error']['code'] ?? 0 ); + throw new \RuntimeException( + sprintf( + /* translators: %1$d: upstream HTTP code, %2$s: error message */ + __( 'OpenRouter upstream error (code %1$d): %2$s', 'prautoblogger' ), + $upstream_code, + $data['error']['message'] + ) + ); + } + // Validate we have a choices array with at least one message. if ( ! isset( $data['choices'][0]['message'] ) ) { // Log the raw shape so we can diagnose what the model actually returned. diff --git a/prautoblogger.php b/prautoblogger.php index 0a35e05..366915e 100644 --- a/prautoblogger.php +++ b/prautoblogger.php @@ -57,7 +57,7 @@ define( 'PRAUTOBLOGGER_DEFAULT_EDITOR_MODEL', 'google/gemini-2.5-flash-lite' ); // Default provider is OpenRouter (higher quality); Cloudflare is the fallback. // Default model is FLUX.2 Pro via OpenRouter (~$0.03/image, good quality). define( 'PRAUTOBLOGGER_DEFAULT_IMAGE_PROVIDER', 'openrouter' ); -define( 'PRAUTOBLOGGER_DEFAULT_IMAGE_MODEL', 'black-forest-labs/flux-2-pro' ); +define( 'PRAUTOBLOGGER_DEFAULT_IMAGE_MODEL', 'google/gemini-2.5-flash-image' ); // CEO-approved image style suffix (decision locked 2026-04-15). Users may // override via Settings → Images → Style Suffix; this constant is the