From c8595e05de7385154d98b1676b736e2d328c500d Mon Sep 17 00:00:00 2001 From: peptiderepo Date: Sat, 18 Apr 2026 16:08:36 +0700 Subject: [PATCH] Fix duplicate image, remove hardcoded model references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes in one: 1. Duplicate image: prepend_caption_to_post() was embedding the full image + caption as a
block, but the theme already displays the featured image. Now only the caption text is prepended — no second copy of the image. 2. Hardcoded model names: removed all FLUX.1/FLUX.2 references from model-agnostic code (pipeline, prompt builder, sideloader, OpenRouter provider, interface, admin labels). The Cloudflare provider still references FLUX since it specifically wraps that model family. 3. Test alignment: OpenRouterImagePricingTest was asserting against removed models (flux-2-pro, seedream). Updated to match current COST_PER_IMAGE entries (Gemini Flash, GPT-5 Image). Co-Authored-By: Claude Opus 4.6 --- .../admin/class-settings-fields-extended.php | 6 +-- .../core/class-image-media-sideloader.php | 2 +- includes/core/class-image-pipeline.php | 33 +++++------- includes/core/class-image-prompt-builder.php | 2 +- .../class-open-router-image-provider.php | 2 +- .../providers/interface-image-provider.php | 2 +- .../services/interface-model-registry.php | 2 +- prautoblogger.php | 5 +- .../Providers/OpenRouterImagePricingTest.php | 50 +++++++------------ 9 files changed, 41 insertions(+), 63 deletions(-) diff --git a/includes/admin/class-settings-fields-extended.php b/includes/admin/class-settings-fields-extended.php index 81621b4..20e3d28 100644 --- a/includes/admin/class-settings-fields-extended.php +++ b/includes/admin/class-settings-fields-extended.php @@ -181,8 +181,8 @@ class PRAutoBlogger_Settings_Fields_Extended { 'section' => 'prautoblogger_images', 'default' => 'openrouter', 'options' => [ - 'openrouter' => __( 'OpenRouter (FLUX.2 Pro, Seedream, GPT-Image)', 'prautoblogger' ), - 'cloudflare' => __( 'Cloudflare Workers AI (FLUX.1 schnell)', 'prautoblogger' ), + 'openrouter' => __( 'OpenRouter (multiple models available)', 'prautoblogger' ), + 'cloudflare' => __( 'Cloudflare Workers AI', 'prautoblogger' ), ], 'description' => __( 'OpenRouter reuses your existing API key and offers higher-quality models. Cloudflare is cheaper but lower quality.', 'prautoblogger' ), ], @@ -193,7 +193,7 @@ class PRAutoBlogger_Settings_Fields_Extended { 'section' => 'prautoblogger_images', 'default' => PRAUTOBLOGGER_DEFAULT_IMAGE_MODEL, 'capability' => 'image_generation', - 'description' => __( 'Pick a model from your selected provider. FLUX.2 Pro recommended for quality.', 'prautoblogger' ), + 'description' => __( 'Pick a model from your selected provider.', 'prautoblogger' ), 'badge' => __( 'Quality', 'prautoblogger' ), ], [ diff --git a/includes/core/class-image-media-sideloader.php b/includes/core/class-image-media-sideloader.php index b2591c6..b0feb03 100644 --- a/includes/core/class-image-media-sideloader.php +++ b/includes/core/class-image-media-sideloader.php @@ -145,7 +145,7 @@ class PRAutoBlogger_Image_Media_Sideloader { gmdate( 'Y-m-d_H-i-s' ) ); - return $base . '.png'; // Default to PNG since FLUX.1 outputs PNG. + return $base . '.png'; // Default to PNG — most image models output PNG. } /** diff --git a/includes/core/class-image-pipeline.php b/includes/core/class-image-pipeline.php index eba78cc..46ce1f8 100644 --- a/includes/core/class-image-pipeline.php +++ b/includes/core/class-image-pipeline.php @@ -19,12 +19,12 @@ class PRAutoBlogger_Image_Pipeline { /** * Default image dimensions (landscape). */ - // FLUX.1 requires both dimensions to be divisible by 8. + // Dimensions must be divisible by 8 (required by some providers). // 1200×632 is the closest 8-aligned pair to the standard OG image size (1200×630). private const DEFAULT_WIDTH = 1200; private const DEFAULT_HEIGHT = 632; - /** @var PRAutoBlogger_Image_Provider_Interface Image gen provider (FLUX.1 etc). */ + /** @var PRAutoBlogger_Image_Provider_Interface Image gen provider. */ private PRAutoBlogger_Image_Provider_Interface $provider; /** @var PRAutoBlogger_Image_Prompt_Builder Builds scene + caption from article data. */ @@ -248,15 +248,14 @@ class PRAutoBlogger_Image_Pipeline { } /** - * Prepend a comic caption as a styled figcaption block to post content. + * Prepend the comic caption as styled text at the top of the post content. * - * Inserts a
block at the top of the post containing the featured - * image and a
with the comic punchline. Uses inline styles - * for portability across themes. + * Only inserts the caption — NOT the image. The theme already displays the + * featured image, so embedding it again would cause a duplicate. * - * @param int $post_id Post ID. - * @param int $attachment_id Featured image attachment ID. - * @param string $caption Caption text (the comic punchline). + * @param int $post_id Post ID. + * @param int $attachment_id Featured image attachment ID (for meta storage only). + * @param string $caption Caption text (the comic punchline). */ private function prepend_caption_to_post( int $post_id, int $attachment_id, string $caption ): void { $post = get_post( $post_id ); @@ -264,23 +263,15 @@ class PRAutoBlogger_Image_Pipeline { return; } - $img_url = wp_get_attachment_url( $attachment_id ); - $alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ); - - // Build a self-contained figure block with the comic image and caption. - $figure = sprintf( - '
' - . '%s' - . '
— "%s"
' - . '
', - esc_url( $img_url ), - esc_attr( $alt ), + // Caption-only block — the theme handles the featured image display. + $caption_html = sprintf( + '

— "%s"

', esc_html( $caption ) ); wp_update_post( [ 'ID' => $post_id, - 'post_content' => $figure . "\n\n" . $post->post_content, + 'post_content' => $caption_html . "\n\n" . $post->post_content, ] ); } } diff --git a/includes/core/class-image-prompt-builder.php b/includes/core/class-image-prompt-builder.php index a9a0b90..689776e 100644 --- a/includes/core/class-image-prompt-builder.php +++ b/includes/core/class-image-prompt-builder.php @@ -5,7 +5,7 @@ declare(strict_types=1); * Builds image prompts from article content or source data. * * Uses a cheap LLM (via OpenRouter) to distill article content into concise - * visual scene descriptions optimised for FLUX.1. Falls back to rule-based + * visual scene descriptions optimised for image generation. Falls back to rule-based * synthesis if the LLM call fails, so image generation never blocks on a * prompt-rewriting outage. * diff --git a/includes/providers/class-open-router-image-provider.php b/includes/providers/class-open-router-image-provider.php index c3acb06..b5fc5ef 100644 --- a/includes/providers/class-open-router-image-provider.php +++ b/includes/providers/class-open-router-image-provider.php @@ -2,7 +2,7 @@ declare(strict_types=1); /** - * OpenRouter image provider — FLUX.2, Seedream, GPT-Image, Gemini Flash. + * OpenRouter image provider — model-agnostic, works with any image model. * * Calls the standard OpenRouter chat/completions endpoint with * `modalities: ["image"]`. Reuses the same API key and optional diff --git a/includes/providers/interface-image-provider.php b/includes/providers/interface-image-provider.php index 33701c8..fde16a5 100644 --- a/includes/providers/interface-image-provider.php +++ b/includes/providers/interface-image-provider.php @@ -13,7 +13,7 @@ declare(strict_types=1); * content generation run, after editorial review, before publish. * Dependencies: None at interface level — implementations wrap a specific HTTP API. * - * @see class-cloudflare-image-provider.php — Default implementation (FLUX.1 via Workers AI). + * @see class-cloudflare-image-provider.php — Cloudflare Workers AI implementation. * @see interface-llm-provider.php — Parallel pattern for LLM providers. * @see ARCHITECTURE.md — External API Integrations table. */ diff --git a/includes/services/interface-model-registry.php b/includes/services/interface-model-registry.php index a527bd4..3ca9606 100644 --- a/includes/services/interface-model-registry.php +++ b/includes/services/interface-model-registry.php @@ -5,7 +5,7 @@ declare(strict_types=1); * Contract for a provider-specific AI model registry. * * Phase 1 (v1): single implementation for OpenRouter (`class-open-router-model-registry.php`). - * Phase 3: a second implementation wraps Cloudflare Workers AI FLUX model data. A + * Phase 3: a second implementation wraps Cloudflare Workers AI model data. A * higher-level dispatcher iterates registered registries, calls each with the same * capability, and unions the results — the per-registry interface stays unchanged. * diff --git a/prautoblogger.php b/prautoblogger.php index 23bf323..ca19052 100644 --- a/prautoblogger.php +++ b/prautoblogger.php @@ -54,8 +54,9 @@ define( 'PRAUTOBLOGGER_DEFAULT_WRITING_MODEL', 'google/gemini-2.5-flash-lite' ); define( 'PRAUTOBLOGGER_DEFAULT_EDITOR_MODEL', 'google/gemini-2.5-flash-lite' ); // Image generation defaults. -// Default provider is OpenRouter (higher quality); Cloudflare is the fallback. -// Default model is FLUX.2 Pro via OpenRouter (~$0.03/image, good quality). +// Default provider is OpenRouter; Cloudflare is the fallback. +// Default model is Gemini 2.5 Flash Image ($0.005/image, good for comics). +// User can override via Settings → Images → Image Model. define( 'PRAUTOBLOGGER_DEFAULT_IMAGE_PROVIDER', 'openrouter' ); define( 'PRAUTOBLOGGER_DEFAULT_IMAGE_MODEL', 'google/gemini-2.5-flash-image' ); diff --git a/tests/unit/Providers/OpenRouterImagePricingTest.php b/tests/unit/Providers/OpenRouterImagePricingTest.php index 9c9e969..7c3923a 100644 --- a/tests/unit/Providers/OpenRouterImagePricingTest.php +++ b/tests/unit/Providers/OpenRouterImagePricingTest.php @@ -22,61 +22,47 @@ class OpenRouterImagePricingTest extends BaseTestCase { } ); } - /** Default model constant is FLUX.2 Pro. */ - public function test_default_model_constant(): void { - $this->assertSame( - 'black-forest-labs/flux-2-pro', - \PRAutoBlogger_OpenRouter_Image_Pricing::DEFAULT_MODEL - ); - } - - /** Empty hint resolves to default model. */ + /** Empty hint resolves to the PRAUTOBLOGGER_DEFAULT_IMAGE_MODEL constant. */ public function test_resolve_model_empty_hint_uses_default(): void { $pricing = new \PRAutoBlogger_OpenRouter_Image_Pricing(); $model = $pricing->resolve_model( '' ); - $this->assertSame( 'black-forest-labs/flux-2-pro', $model ); + $this->assertSame( PRAUTOBLOGGER_DEFAULT_IMAGE_MODEL, $model ); } - /** Valid hint resolves to itself. */ + /** Explicit hint resolves to itself, regardless of what default is set. */ public function test_resolve_model_valid_hint(): void { $pricing = new \PRAutoBlogger_OpenRouter_Image_Pricing(); - $model = $pricing->resolve_model( 'bytedance/seedream-3.0' ); - $this->assertSame( 'bytedance/seedream-3.0', $model ); + $model = $pricing->resolve_model( 'openai/gpt-5-image' ); + $this->assertSame( 'openai/gpt-5-image', $model ); } - /** Unknown model falls back to default. */ - public function test_resolve_model_unknown_falls_back_to_default(): void { + /** Gemini Flash Image costs $0.005/image. */ + public function test_estimate_cost_gemini_flash(): void { $pricing = new \PRAutoBlogger_OpenRouter_Image_Pricing(); - $model = $pricing->resolve_model( 'nonexistent/model-999' ); - $this->assertSame( 'black-forest-labs/flux-2-pro', $model ); + $cost = $pricing->estimate_cost( 1200, 632, 'google/gemini-2.5-flash-image' ); + $this->assertEqualsWithDelta( 0.005, $cost, 0.000001 ); } - /** FLUX.2 Pro costs $0.03/image. */ - public function test_estimate_cost_flux2_pro(): void { + /** GPT-5 Image costs $0.08/image. */ + public function test_estimate_cost_gpt5_image(): void { $pricing = new \PRAutoBlogger_OpenRouter_Image_Pricing(); - $cost = $pricing->estimate_cost( 1200, 632, 'black-forest-labs/flux-2-pro' ); - $this->assertEqualsWithDelta( 0.03, $cost, 0.000001 ); + $cost = $pricing->estimate_cost( 1200, 632, 'openai/gpt-5-image' ); + $this->assertEqualsWithDelta( 0.08, $cost, 0.000001 ); } - /** Seedream costs $0.003/image. */ - public function test_estimate_cost_seedream(): void { - $pricing = new \PRAutoBlogger_OpenRouter_Image_Pricing(); - $cost = $pricing->estimate_cost( 1200, 632, 'bytedance/seedream-3.0' ); - $this->assertEqualsWithDelta( 0.003, $cost, 0.000001 ); - } - - /** Unknown model in estimate_cost falls back to default pricing. */ + /** Unknown model falls back to conservative $0.05 estimate. */ public function test_estimate_cost_unknown_model(): void { $pricing = new \PRAutoBlogger_OpenRouter_Image_Pricing(); $cost = $pricing->estimate_cost( 1200, 632, 'unknown/model' ); - $this->assertEqualsWithDelta( 0.03, $cost, 0.000001 ); + $this->assertEqualsWithDelta( 0.05, $cost, 0.000001 ); } - /** get_model_costs returns a non-empty map. */ + /** get_model_costs returns a non-empty map with known models. */ public function test_get_model_costs(): void { $costs = \PRAutoBlogger_OpenRouter_Image_Pricing::get_model_costs(); $this->assertIsArray( $costs ); - $this->assertArrayHasKey( 'black-forest-labs/flux-2-pro', $costs ); + $this->assertArrayHasKey( 'google/gemini-2.5-flash-image', $costs ); + $this->assertArrayHasKey( 'openai/gpt-5-image', $costs ); $this->assertGreaterThan( 0, count( $costs ) ); } }