diff --git a/includes/admin/class-admin-page.php b/includes/admin/class-admin-page.php index a0ed97f..e8cd9ac 100755 --- a/includes/admin/class-admin-page.php +++ b/includes/admin/class-admin-page.php @@ -231,7 +231,7 @@ class PRAutoBlogger_Admin_Page { $option_name = substr( $filter, strlen( 'sanitize_option_' ) ); } - $encrypted = [ 'prautoblogger_openrouter_api_key', 'prautoblogger_ga4_credentials_json', 'prautoblogger_cloudflare_ai_token', 'prautoblogger_runware_api_key' ]; + $encrypted = [ 'prautoblogger_openrouter_api_key', 'prautoblogger_ga4_credentials_json', 'prautoblogger_runware_api_key' ]; if ( in_array( $option_name, $encrypted, true ) ) { // Empty value means password field wasn't touched — keep existing. if ( '' === $value ) { diff --git a/includes/admin/class-image-model-registry.php b/includes/admin/class-image-model-registry.php index 9fd35c6..1a38601 100644 --- a/includes/admin/class-image-model-registry.php +++ b/includes/admin/class-image-model-registry.php @@ -22,7 +22,7 @@ class PRAutoBlogger_Image_Model_Registry { * Each entry contains: * - id: Model identifier used in API calls. * - name: Human-readable display name. - * - provider: 'runware' | 'openrouter' | 'cloudflare'. + * - provider: 'runware' | 'openrouter'. * - cost_per_image: Estimated USD cost per generation. * - capabilities: Array of capability tags. * - description: Short description for the admin UI. @@ -90,14 +90,6 @@ class PRAutoBlogger_Image_Model_Registry { 'capabilities' => [ 'image_generation' ], 'description' => __( 'OpenAI premium. Photorealistic.', 'prautoblogger' ), ], - [ - 'id' => 'flux-1-schnell', - 'name' => 'FLUX.1 schnell (Cloudflare)', - 'provider' => 'cloudflare', - 'cost_per_image' => 0.0007, - 'capabilities' => [ 'image_generation' ], - 'description' => __( 'Legacy Cloudflare fallback. Use Runware FLUX instead.', 'prautoblogger' ), - ], ]; } @@ -106,7 +98,7 @@ class PRAutoBlogger_Image_Model_Registry { * model id is not in the registry. * * @param string $model_id Model slug from the admin UI. - * @return string Provider id ('runware' | 'openrouter' | 'cloudflare' | ''). + * @return string Provider id ('runware' | 'openrouter' | ''). */ public static function provider_for( string $model_id ): string { foreach ( self::get_models() as $model ) { diff --git a/includes/admin/class-settings-fields-extended.php b/includes/admin/class-settings-fields-extended.php index f4bfcd0..7093a2d 100644 --- a/includes/admin/class-settings-fields-extended.php +++ b/includes/admin/class-settings-fields-extended.php @@ -221,25 +221,9 @@ class PRAutoBlogger_Settings_Fields_Extended { 'section' => 'prautoblogger_images', 'default' => PRAUTOBLOGGER_DEFAULT_IMAGE_MODEL, 'capability' => 'image_generation', - 'description' => __( 'Pick an image model. The provider (OpenRouter or Cloudflare Workers AI) is derived from the model registry on save, so mismatched pairs are no longer possible.', 'prautoblogger' ), + 'description' => __( 'Pick an image model. The provider (Runware or OpenRouter) is derived from the model registry on save, so mismatched pairs are no longer possible.', 'prautoblogger' ), 'badge' => __( 'Quality', 'prautoblogger' ), ], - [ - 'id' => 'prautoblogger_cloudflare_ai_token', - 'label' => __( 'Cloudflare API Token', 'prautoblogger' ), - 'type' => 'password', - 'section' => 'prautoblogger_images', - 'description' => __( 'Only needed if using Cloudflare provider. Workers AI token with Read + Edit scope.', 'prautoblogger' ), - 'icon' => '🔑', - ], - [ - 'id' => 'prautoblogger_cloudflare_account_id', - 'label' => __( 'Cloudflare Account ID', 'prautoblogger' ), - 'type' => 'text', - 'section' => 'prautoblogger_images', - 'default' => '', - 'description' => __( 'Only needed if using Cloudflare provider.', 'prautoblogger' ), - ], [ 'id' => 'prautoblogger_runware_api_key', 'label' => __( 'Runware API Key', 'prautoblogger' ), @@ -270,15 +254,7 @@ class PRAutoBlogger_Settings_Fields_Extended { 'type' => 'toggle', 'section' => 'prautoblogger_images', 'default' => '1', - 'description' => __( 'When the provider rejects an image prompt as NSFW (e.g. Cloudflare code 3030), retry once with a generic fallback scene built from the article title. Disable to fail fast if the filter gets trigger-happy.', 'prautoblogger' ), - ], - [ - 'id' => 'prautoblogger_cf_image_via_gateway', - 'label' => __( 'Route Cloudflare Images via AI Gateway', 'prautoblogger' ), - 'type' => 'toggle', - 'section' => 'prautoblogger_images', - 'default' => '1', - 'description' => __( 'Route Cloudflare Workers AI image calls through the Cloudflare AI Gateway (requires the AI Gateway URL configured under API Keys). Adds response caching, cost logging, and rate-limiting. Turn off to hit the direct Workers AI API — only needed if the gateway route regresses.', 'prautoblogger' ), + 'description' => __( 'When the provider rejects an image prompt as NSFW, retry once with a generic fallback scene built from the article title. Disable to fail fast if the filter gets trigger-happy.', 'prautoblogger' ), ], ]; } diff --git a/includes/class-ajax-handlers.php b/includes/class-ajax-handlers.php index 63283cd..c8e48a2 100644 --- a/includes/class-ajax-handlers.php +++ b/includes/class-ajax-handlers.php @@ -42,7 +42,7 @@ class PRAutoBlogger_Ajax_Handlers { return; } - // Image generation can take 20-30s on Cloudflare Workers AI. + // Image generation can take 20-30s (Runware FLUX + OpenRouter image models). // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged @set_time_limit( 120 ); diff --git a/includes/core/class-image-nsfw-retry.php b/includes/core/class-image-nsfw-retry.php index c5c0ebb..8b0ed71 100644 --- a/includes/core/class-image-nsfw-retry.php +++ b/includes/core/class-image-nsfw-retry.php @@ -4,8 +4,8 @@ declare(strict_types=1); /** * Single-shot retry of NSFW-blocked image slots with a sanitized prompt. * - * When an image provider (currently only Cloudflare Workers AI) rejects a - * prompt via its content filter, its batch result carries + * When an image provider rejects a prompt via its content filter, its + * batch result carries * `['error_type' => 'nsfw_blocked']`. This helper reruns just the blocked * slot once with the rule-based fallback prompt (article title + style * suffix, no LLM rewrite) and merges the new result back into the batch. diff --git a/includes/core/class-image-pipeline.php b/includes/core/class-image-pipeline.php index 6d303fe..d5decc4 100644 --- a/includes/core/class-image-pipeline.php +++ b/includes/core/class-image-pipeline.php @@ -38,7 +38,7 @@ class PRAutoBlogger_Image_Pipeline { * * When no provider is injected, the constructor reads the * `prautoblogger_image_provider` setting and instantiates the - * matching concrete class ('runware', 'openrouter', or 'cloudflare'). + * matching concrete class ('runware' or 'openrouter'). * * @param PRAutoBlogger_Image_Provider_Interface|null $provider Optional provider override. * @param PRAutoBlogger_Cost_Tracker|null $cost_tracker Optional cost tracker. @@ -56,17 +56,19 @@ class PRAutoBlogger_Image_Pipeline { /** * Instantiate the image provider based on the admin setting. * + * Returns Runware by default when the setting is unrecognised — + * Cloudflare Workers AI was removed as a provider in v0.10.0; legacy + * 'cloudflare' values are migrated at admin_init, but an unmigrated + * install should still get a working fallback rather than a fatal. + * * @return PRAutoBlogger_Image_Provider_Interface */ private static function create_default_provider(): PRAutoBlogger_Image_Provider_Interface { $provider_id = (string) get_option( 'prautoblogger_image_provider', PRAUTOBLOGGER_DEFAULT_IMAGE_PROVIDER ); - if ( 'runware' === $provider_id ) { - return new PRAutoBlogger_Runware_Image_Provider(); - } if ( 'openrouter' === $provider_id ) { return new PRAutoBlogger_OpenRouter_Image_Provider(); } - return new PRAutoBlogger_Cloudflare_Image_Provider(); + return new PRAutoBlogger_Runware_Image_Provider(); } /** diff --git a/includes/providers/class-image-nsfw-blocked.php b/includes/providers/class-image-nsfw-blocked.php index a2d18ee..1b50d78 100644 --- a/includes/providers/class-image-nsfw-blocked.php +++ b/includes/providers/class-image-nsfw-blocked.php @@ -4,18 +4,18 @@ declare(strict_types=1); /** * Exception raised when an image provider rejects a prompt as NSFW. * - * Cloudflare Workers AI returns HTTP 400 with an `errors[].code === 3030` - * body when its content filter blocks a prompt. We catch that shape in - * the provider and raise this typed exception so the image pipeline can - * distinguish a content-filter rejection from a generic 4xx and retry - * once with a sanitized fallback prompt. + * Image providers (Runware, OpenRouter) return provider-specific error + * shapes when their content filters block a prompt. We catch those shapes + * in each provider and raise this typed exception so the image pipeline + * can distinguish a content-filter rejection from a generic 4xx and + * retry once with a sanitized fallback prompt. * - * Triggered by: PRAutoBlogger_Cloudflare_Image_Provider on NSFW 4xx. + * Triggered by: image providers on NSFW-filter rejection. * Dependencies: None. * - * @see providers/class-cloudflare-image-provider.php — Raises this. - * @see core/class-image-nsfw-retry.php — Catches + rebuilds prompt. - * @see https://developers.cloudflare.com/workers-ai/models/flux-1-schnell/ — Upstream error codes. + * @see providers/class-runware-image-provider.php — May raise this. + * @see providers/class-open-router-image-provider.php — May raise this. + * @see core/class-image-nsfw-retry.php — Catches + rebuilds prompt. */ class PRAutoBlogger_Image_NSFW_Blocked extends \RuntimeException { diff --git a/includes/providers/class-open-router-image-pricing.php b/includes/providers/class-open-router-image-pricing.php index 978ebdb..9613744 100644 --- a/includes/providers/class-open-router-image-pricing.php +++ b/includes/providers/class-open-router-image-pricing.php @@ -4,7 +4,7 @@ declare(strict_types=1); /** * Pricing + model-id resolution helpers for the OpenRouter image provider. * - * Mirrors the Cloudflare pricing helper pattern. Each model's cost per image + * Mirrors the Runware pricing helper pattern. Each model's cost per image * is a flat rate sourced from OpenRouter's pricing page (April 2026). * * Triggered by: PRAutoBlogger_OpenRouter_Image_Provider (both generate_image() @@ -12,7 +12,7 @@ declare(strict_types=1); * Dependencies: None — pure calculation + option reads. * * @see class-openrouter-image-provider.php — Caller. - * @see class-cloudflare-image-pricing.php — Sibling pattern for Cloudflare. + * @see class-runware-image-pricing.php — Sibling pattern for Runware. */ class PRAutoBlogger_OpenRouter_Image_Pricing { diff --git a/includes/providers/interface-image-provider.php b/includes/providers/interface-image-provider.php index 4e8b804..7a6a8d3 100644 --- a/includes/providers/interface-image-provider.php +++ b/includes/providers/interface-image-provider.php @@ -2,7 +2,7 @@ declare(strict_types=1); /** - * Contract for any image generation provider (Cloudflare Workers AI, DALL-E, etc.). + * Contract for any image generation provider (Runware FLUX, OpenRouter Gemini, etc.). * * Consumers depend on this interface, never on a concrete provider — swapping * providers means writing one new class, not refactoring the pipeline. This @@ -13,7 +13,8 @@ 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 — Cloudflare Workers AI implementation. + * @see class-runware-image-provider.php — Runware FLUX implementation (default). + * @see class-open-router-image-provider.php — OpenRouter image implementation. * @see interface-llm-provider.php — Parallel pattern for LLM providers. * @see ARCHITECTURE.md — External API Integrations table. */ @@ -79,7 +80,7 @@ interface PRAutoBlogger_Image_Provider_Interface { /** * Machine-readable provider identifier used in logs and settings. * - * @return string Lowercase identifier, e.g. 'cloudflare_workers_ai'. + * @return string Lowercase identifier, e.g. 'runware' or 'openrouter'. */ public function get_provider_name(): string; diff --git a/includes/services/interface-model-registry.php b/includes/services/interface-model-registry.php index 3ca9606..6fe192e 100644 --- a/includes/services/interface-model-registry.php +++ b/includes/services/interface-model-registry.php @@ -5,9 +5,9 @@ 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 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. + * Phase 3: a second implementation wraps Runware 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. * * Implementations MUST: * - Store the normalized registry as a WP option + transient cache layer. @@ -86,7 +86,7 @@ interface PRAutoBlogger_Model_Registry_Interface { public function get_fetched_at(): int; /** - * Machine-readable provider identifier (e.g. 'openrouter', 'cloudflare_workers_ai'). + * Machine-readable provider identifier (e.g. 'openrouter', 'runware'). * * @return string */ diff --git a/prautoblogger.php b/prautoblogger.php index e07fa32..cd6d609 100644 --- a/prautoblogger.php +++ b/prautoblogger.php @@ -54,7 +54,7 @@ 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 Runware; OpenRouter (Gemini) + Cloudflare remain as alternates. +// Default provider is Runware; OpenRouter (Gemini) remains as the alternate. // Default model is FLUX.1 schnell via Runware (~$0.0006/image, ~2s). CEO // decision 2026-04-21 after comic A/B: schnell's looser fidelity reads as // editorial-cartoon style and is 65× cheaper than Nano Banana. Users can