From 1a2af595c6d47fb1b1d428834781312a8fbc28fa Mon Sep 17 00:00:00 2001 From: peptiderepo Date: Sat, 18 Apr 2026 12:17:05 +0700 Subject: [PATCH] fix: attach images immediately + upgrade image prompts to v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes: 1. Image attachment: moved set_post_thumbnail() and Image B post meta into generate_and_attach_images() so they persist immediately after each image generates. Previously the caller set them after both images completed — if the process timed out (common with GPT-5 Image at ~30s/image), the featured image was never attached and Image B was never attempted. 2. Image prompts: replaced FLUX.1-optimized rewriter system prompt and 90s infomercial style suffix with prompts optimized for Gemini Flash Image and GPT-5 Image. New style: premium scientific lifestyle photography with golds/teals color grading. Includes one-time migration to update stored style suffix. Also logs when Image B is skipped due to missing source data (previously silent), helping diagnose why some posts only get one image. Co-Authored-By: Claude Opus 4.6 --- includes/class-executor.php | 12 ++-------- includes/class-prautoblogger.php | 13 +++++++++++ includes/core/class-image-pipeline.php | 22 +++++++++++++++++++ includes/core/class-image-prompt-builder.php | 23 ++++++++++++-------- includes/core/class-post-assembler.php | 20 ++++------------- prautoblogger.php | 14 +++++++----- 6 files changed, 63 insertions(+), 41 deletions(-) diff --git a/includes/class-executor.php b/includes/class-executor.php index f474b6e..b6ca443 100644 --- a/includes/class-executor.php +++ b/includes/class-executor.php @@ -152,18 +152,10 @@ class PRAutoBlogger_Executor { is_array( $source_ids ) ? array_map( 'absint', $source_ids ) : [] ); + // The pipeline now sets featured image and Image B meta + // internally, immediately after each image generates. $result = $pipeline->generate_and_attach_images( $post_id, $article_data, $source_data ); - // Set featured image if Image A was generated. - if ( ! empty( $result['image_a_id'] ) ) { - set_post_thumbnail( $post_id, $result['image_a_id'] ); - } - - // Store Image B in post meta if generated. - if ( ! empty( $result['image_b_id'] ) ) { - update_post_meta( $post_id, '_prautoblogger_image_b_id', $result['image_b_id'] ); - } - wp_send_json_success( $result ); } catch ( \Throwable $e ) { PRAutoBlogger_Logger::instance()->error( diff --git a/includes/class-prautoblogger.php b/includes/class-prautoblogger.php index 31c1d9b..b2cc1ea 100644 --- a/includes/class-prautoblogger.php +++ b/includes/class-prautoblogger.php @@ -123,6 +123,19 @@ class PRAutoBlogger { update_option( 'prautoblogger_migrated_gemini_flash_lite', '1' ); } + // One-time migration: replace 90s infomercial style suffix with premium + // science editorial style. The old pastiche rendered poorly on Gemini/GPT-5 + // Image models. Only overwrite if the stored value IS the old default — if + // the user customized it, respect their choice. + if ( ! get_option( 'prautoblogger_migrated_style_suffix_v2' ) ) { + $old_infomercial = 'Style: a screengrab from a 1995 late-night cable TV infomercial'; + $current = get_option( 'prautoblogger_image_style_suffix', '' ); + if ( '' === $current || false !== strpos( $current, $old_infomercial ) ) { + update_option( 'prautoblogger_image_style_suffix', PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_SUFFIX ); + } + update_option( 'prautoblogger_migrated_style_suffix_v2', '1' ); + } + // One-time migration: re-wrap existing encrypted values with "enc:" prefix. if ( ! get_option( 'prautoblogger_migrated_enc_prefix' ) ) { $enc_options = [ 'prautoblogger_openrouter_api_key', 'prautoblogger_ga4_credentials_json' ]; diff --git a/includes/core/class-image-pipeline.php b/includes/core/class-image-pipeline.php index f23b82c..3c2e766 100644 --- a/includes/core/class-image-pipeline.php +++ b/includes/core/class-image-pipeline.php @@ -138,6 +138,15 @@ class PRAutoBlogger_Image_Pipeline { $image_a_result = $this->generate_image_a( $post_id, $article_data ); if ( ! is_wp_error( $image_a_result ) && isset( $image_a_result['attachment_id'] ) ) { $result['image_a_id'] = $image_a_result['attachment_id']; + + // Set featured image immediately so it persists even if the + // process times out before Image B finishes or before the + // caller gets to handle the return value. + set_post_thumbnail( $post_id, $image_a_result['attachment_id'] ); + PRAutoBlogger_Logger::instance()->info( + sprintf( 'Set featured image (attachment %d) for post %d', $image_a_result['attachment_id'], $post_id ), + 'image_pipeline' + ); } else { $result['errors'][] = is_wp_error( $image_a_result ) ? $image_a_result->get_error_message() @@ -152,6 +161,14 @@ class PRAutoBlogger_Image_Pipeline { $image_b_result = $this->generate_image_b( $post_id, $source_data ); if ( ! is_wp_error( $image_b_result ) && isset( $image_b_result['attachment_id'] ) ) { $result['image_b_id'] = $image_b_result['attachment_id']; + + // Store Image B reference immediately for the same + // timeout-resilience reason as Image A above. + update_post_meta( $post_id, '_prautoblogger_image_b_id', $image_b_result['attachment_id'] ); + PRAutoBlogger_Logger::instance()->info( + sprintf( 'Stored Image B (attachment %d) for post %d', $image_b_result['attachment_id'], $post_id ), + 'image_pipeline' + ); } else { $result['errors'][] = is_wp_error( $image_b_result ) ? $image_b_result->get_error_message() @@ -160,6 +177,11 @@ class PRAutoBlogger_Image_Pipeline { if ( ! is_wp_error( $image_b_result ) ) { $result['cost_usd'] += $image_b_result['cost_usd'] ?? 0.0; } + } else { + PRAutoBlogger_Logger::instance()->info( + sprintf( 'Image B skipped for post %d: no source data available.', $post_id ), + 'image_pipeline' + ); } return $result; diff --git a/includes/core/class-image-prompt-builder.php b/includes/core/class-image-prompt-builder.php index b35183f..d0b32a5 100644 --- a/includes/core/class-image-prompt-builder.php +++ b/includes/core/class-image-prompt-builder.php @@ -28,17 +28,22 @@ class PRAutoBlogger_Image_Prompt_Builder { * code review, not an admin-panel click. */ private const REWRITER_SYSTEM_PROMPT = <<<'PROMPT' -You are an image-prompt specialist for FLUX.1, a text-to-image model. +You are an image-prompt specialist for premium AI image models (Gemini Flash Image, GPT-5 Image). -Given an article title and summary, output ONE short paragraph (2-4 sentences, under 60 words) describing a concrete visual scene that would make a compelling featured image for the article. +Given an article title and summary about peptides, supplements, or biohacking, write ONE vivid scene description (2-3 sentences, under 80 words) for a compelling featured image. Emphasize: +- OBJECTS and MATERIALS: lab glassware, peptide vials, molecular structures, syringes, supplement bottles, biometric devices, microscopes, fitness equipment, test tubes +- LIGHTING and DEPTH: bright directional studio lighting, selective focus, rich warm/cool color contrasts +- COMPOSITION: dynamic framing with clear focal point, professional product photography perspective, sense of scale and precision +- MOOD: scientific credibility paired with aspirational wellness — premium feel, approachable, engaging -Rules: -- Describe OBJECTS, SETTING, LIGHTING, COMPOSITION, and MOOD — not abstract concepts. -- Never include text, words, labels, logos, or watermarks in the scene. -- Never describe people's faces in detail (FLUX.1 struggles with faces). Use silhouettes, hands, or over-the-shoulder framings instead. -- Focus on the article's core SUBJECT as a physical thing: lab equipment, supplements, syringes, molecules, gym settings, packaging, etc. -- Do NOT include any style direction — that is appended separately. -- Output ONLY the scene description. No preamble, no explanation, no quotes. +Avoid: +- Abstract concepts; show concrete objects only +- Text, logos, watermarks, or readable labels +- Detailed human faces (use hands, torso, silhouettes, or exclude people) +- Multiple competing visual elements; keep the subject dominant +- Generic stock-photo composition + +Output ONLY the scene description — no preamble, explanation, or quotes. Do NOT include any style direction; that is appended separately. PROMPT; /** diff --git a/includes/core/class-post-assembler.php b/includes/core/class-post-assembler.php index 0d59fbf..eddef07 100644 --- a/includes/core/class-post-assembler.php +++ b/includes/core/class-post-assembler.php @@ -138,24 +138,12 @@ class PRAutoBlogger_Post_Assembler { $source_data = ( new PRAutoBlogger_Source_Collector() )->get_source_data_for_image( $idea->get_source_ids() ); try { + // The pipeline now sets featured image and Image B meta + // internally, immediately after each image generates. This + // ensures attachment persists even if the process times out + // before this method returns. $result = ( new PRAutoBlogger_Image_Pipeline() )->generate_and_attach_images( $post_id, $post_data, $source_data ); - if ( isset( $result['image_a_id'] ) ) { - set_post_thumbnail( $post_id, $result['image_a_id'] ); - PRAutoBlogger_Logger::instance()->info( - sprintf( 'Set featured image (attachment %d) for post %d', $result['image_a_id'], $post_id ), - 'publisher' - ); - } - - if ( isset( $result['image_b_id'] ) ) { - update_post_meta( $post_id, '_prautoblogger_image_b_id', $result['image_b_id'] ); - PRAutoBlogger_Logger::instance()->info( - sprintf( 'Stored Image B (attachment %d) for post %d', $result['image_b_id'], $post_id ), - 'publisher' - ); - } - if ( ! empty( $result['errors'] ) ) { foreach ( $result['errors'] as $error ) { PRAutoBlogger_Logger::instance()->warning( 'Image warning for post ' . $post_id . ': ' . $error, 'publisher' ); diff --git a/prautoblogger.php b/prautoblogger.php index 366915e..25f33a9 100644 --- a/prautoblogger.php +++ b/prautoblogger.php @@ -8,7 +8,7 @@ declare(strict_types=1); * Plugin Name: PRAutoBlogger * Plugin URI: https://peptiderepo.com/prautoblogger * Description: Monitors social media for trending topics, generates SEO-friendly blog posts using AI, and publishes them on a daily schedule with full cost tracking and self-improvement metrics. - * Version: 0.2.7 + * Version: 0.2.8 * Requires at least: 6.0 * Requires PHP: 7.4 * Author: PeptideRepo @@ -35,7 +35,7 @@ if ( ! defined( 'ABSPATH' ) ) { | and limits without magic strings. */ -define( 'PRAUTOBLOGGER_VERSION', '0.2.7' ); +define( 'PRAUTOBLOGGER_VERSION', '0.2.8' ); define( 'PRAUTOBLOGGER_DB_VERSION', '1.1.0' ); define( 'PRAUTOBLOGGER_PLUGIN_FILE', __FILE__ ); define( 'PRAUTOBLOGGER_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); @@ -59,12 +59,14 @@ define( 'PRAUTOBLOGGER_DEFAULT_EDITOR_MODEL', 'google/gemini-2.5-flash-lite' ); define( 'PRAUTOBLOGGER_DEFAULT_IMAGE_PROVIDER', 'openrouter' ); 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 -// fallback the setting reads on first install. +// Default style suffix appended to every image-gen prompt to create a +// cohesive visual brand across all article images. Updated 2026-04-18 +// after switching from FLUX.1 to Gemini/GPT-5 Image models — the old +// 90s infomercial pastiche rendered poorly on the new models. Users may +// override via Settings → Images → Style Suffix. define( 'PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_SUFFIX', - 'Style: a screengrab from a 1995 late-night cable TV infomercial, oversaturated warm colors, VHS tape noise and scan lines, a bright yellow starburst graphic in the top right corner reading NEW, cheap studio lighting, dated 4:3 television framing, deadpan earnest expression, faint lens distortion, tacky low-budget commercial aesthetic.' + 'Style: premium scientific lifestyle photography, professional laboratory setting with warm studio lighting, shallow depth of field, color-graded for wellness branding in rich golds and deep teals, high-end editorial quality.' ); /*