v0.9.0: Runware FLUX.1 schnell as default image provider

New Runware image backend at ~$0.0006/image (FLUX.1 schnell) — 65× cheaper
than Gemini 2.5 Flash Image. Provider mirrors OpenRouter's split: interface
+ provider + support + pricing + batch, all under the 300-line cap. Batch
class uses curl_multi for true parallel A/B dispatch — wall-clock drops to
the slowest single image instead of the sum.

Changes:
- Add PRAutoBlogger_Runware_Image_Provider + Pricing + Support + Batch
- Flip PRAUTOBLOGGER_DEFAULT_IMAGE_PROVIDER/MODEL to runware:100@1
- Add prautoblogger_runware_api_key settings field (encrypted at rest)
- v0.9.0 one-time migration flips legacy default model + re-derives provider
- Wire runware branch into Image_Pipeline::create_default_provider()
- Add runware:100@1 and runware:101@1 to Image_Model_Registry (defaults top)
- Update image prompt builder: no more anthropomorphized molecules;
  prefer concrete human-scale scenes (gym bros, doctors, scientists)
- ARCHITECTURE.md + CHANGELOG.md updated with new integration + decision
- Unit tests for pricing, support, provider; update model registry test

Cost impact at 100 images/day (50 articles × A/B):
- Before (Gemini via OpenRouter): $3.90/day ≈ $117/mo
- After  (FLUX schnell via Runware): $0.06/day ≈ $1.80/mo
Net saving: ~$115/mo
This commit is contained in:
peptiderepo
2026-04-21 15:56:35 +00:00
parent 80a7e11a2b
commit 2cacaa3512
18 changed files with 1613 additions and 28 deletions

View File

@@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
/**
* Pricing + model-id resolution helpers for the Runware image provider.
*
* Each Runware FLUX model has a flat per-image cost sourced from
* runware.ai/pricing (April 2026). Unlike OpenRouter models, Runware
* prices scale slightly with steps — the quoted rate here assumes the
* provider's default step count (4 for schnell, 28 for dev).
*
* Triggered by: PRAutoBlogger_Runware_Image_Provider, PRAutoBlogger_Runware_Image_Batch.
* Dependencies: None — pure calculation + option reads.
*
* @see class-runware-image-provider.php — Primary caller.
* @see class-open-router-image-pricing.php — Sibling pattern for OpenRouter.
*/
class PRAutoBlogger_Runware_Image_Pricing {
/**
* Flat per-image cost (USD) for each supported Runware model, April 2026.
* Update here when Runware reprices or new models launch.
*
* Runware model IDs use `family:variant@version` form. The two FLUX
* variants we support are the CEO-sanctioned defaults.
*
* @var array<string, float>
*/
private const COST_PER_IMAGE = [
// FLUX.1 schnell — 4-step distilled. Our new default (2026-04-21).
'runware:100@1' => 0.0006,
// FLUX.1 dev — 28-step full model. Higher fidelity, ~30x cost.
'runware:101@1' => 0.02,
];
/**
* Default number of inference steps per model. Runware bills per image
* at the model's standard step count; custom step counts may alter price.
*
* @var array<string, int>
*/
private const DEFAULT_STEPS = [
'runware:100@1' => 4,
'runware:101@1' => 28,
];
/**
* Resolve the model identifier from a hint, falling back to site option,
* then to the PRAUTOBLOGGER_DEFAULT_IMAGE_MODEL constant.
*
* If the saved/default model is not a Runware id (e.g. still points at
* a Gemini slug), fall back to schnell — the provider was selected
* explicitly, so the user wants a Runware model.
*
* @param string $hint Caller-supplied model identifier (may be empty).
* @return string Runware model id.
*/
public function resolve_model( string $hint ): string {
$candidate = trim( $hint );
if ( '' === $candidate ) {
$candidate = trim( (string) get_option( 'prautoblogger_image_model', '' ) );
}
if ( '' === $candidate && defined( 'PRAUTOBLOGGER_DEFAULT_IMAGE_MODEL' ) ) {
$candidate = (string) PRAUTOBLOGGER_DEFAULT_IMAGE_MODEL;
}
// If the candidate isn't a known Runware id, fall back to schnell.
if ( '' === $candidate || ! isset( self::COST_PER_IMAGE[ $candidate ] ) ) {
return 'runware:100@1';
}
return $candidate;
}
/**
* Estimate USD cost for a single image at the given model.
*
* Runware FLUX pricing is flat per image at the model's standard step
* count. Width/height are accepted for interface compatibility but do
* not affect cost for the variants we support.
*
* @param int $width Width in pixels (unused for flat pricing).
* @param int $height Height in pixels (unused for flat pricing).
* @param string $model Resolved Runware model id.
* @return float USD cost rounded to 6 decimals.
*/
public function estimate_cost( int $width, int $height, string $model ): float {
if ( isset( self::COST_PER_IMAGE[ $model ] ) ) {
return round( self::COST_PER_IMAGE[ $model ], 6 );
}
// Unknown model — log it and use a conservative $0.01 estimate.
PRAutoBlogger_Logger::instance()->warning(
sprintf( 'No Runware pricing data for model "%s". Using $0.01 estimate.', $model ),
'runware-image'
);
return 0.01;
}
/**
* Get the default step count for a Runware model.
*
* @param string $model Resolved Runware model id.
* @return int Default steps.
*/
public function default_steps( string $model ): int {
return self::DEFAULT_STEPS[ $model ] ?? 4;
}
/**
* Get the full model cost table for admin display.
*
* @return array<string, float> Model id => cost per image in USD.
*/
public static function get_model_costs(): array {
return self::COST_PER_IMAGE;
}
}