Compare commits
5 Commits
fix/opik-a
...
fix/opik-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82ed5227af | ||
|
|
c4e8deb3ef | ||
|
|
c6ee23fcd4 | ||
|
|
60efe08200 | ||
|
|
b9c3e4e7d1 |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -5,20 +5,6 @@ All notable changes to PRAutoBlogger will be documented in this file.
|
|||||||
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project uses [Semantic Versioning](https://semver.org/).
|
and this project uses [Semantic Versioning](https://semver.org/).
|
||||||
|
|
||||||
## [0.12.3] — 2026-04-24
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- **Opik autoloader gap** — `class-autoloader.php` listed `services/` as a scan directory but not `services/opik/`, where all four Opik classes live (`Opik_Client`, `Opik_Trace_Context`, `Opik_Span_Queue`, `Opik_Dispatcher`). Composer's classmap (used in PHPUnit) covers `includes/` recursively so tests passed, but the WordPress runtime autoloader would fatal on first Opik class reference in production. Added `services/opik/` to the scan list.
|
|
||||||
|
|
||||||
|
|
||||||
## [0.12.2] — 2026-04-24
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- **Image style suffix empty-string bypass** — `get_option()` for `prautoblogger_image_style_suffix` only returns the default constant when the key is absent from `wp_options`; an empty string stored in the DB returns `''`, bypassing fallback entirely. Root cause of post #675 rendering photorealistically instead of as a newspaper comic. Fixed all three call sites in `class-image-prompt-builder.php` (lines 85, 107, 272) by extracting a private helper `get_style_suffix()` that treats both absent AND empty string as "use default".
|
|
||||||
|
|
||||||
- **Image cost logged under wrong run_id** — `class-image-pipeline.php` created a fresh cost tracker when invoked without an argument, so image costs were logged under a different `run_id` than the article's other stages. Cost breakdowns aggregating by `run_id` silently excluded the image line item. Fixed by threading the article worker's cost tracker through `PRAutoBlogger_Publisher` and `PRAutoBlogger_Post_Assembler::attach_generated_images()` to the image pipeline constructor. Image costs now share the same `run_id` as analysis, draft, review, and llm_research stages.
|
|
||||||
|
|
||||||
|
|
||||||
## [0.12.1] — 2026-04-23
|
## [0.12.1] — 2026-04-23
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ class PRAutoBlogger_Autoloader {
|
|||||||
'models/',
|
'models/',
|
||||||
'frontend/',
|
'frontend/',
|
||||||
'services/',
|
'services/',
|
||||||
'services/opik/',
|
|
||||||
'ajax/',
|
'ajax/',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -138,13 +138,13 @@ class PRAutoBlogger_Article_Worker {
|
|||||||
: $content;
|
: $content;
|
||||||
|
|
||||||
if ( $auto_publish ) {
|
if ( $auto_publish ) {
|
||||||
$publisher->publish( $final, $idea, $review, $run_id, $this->cost_tracker );
|
$publisher->publish( $final, $idea, $review, $run_id );
|
||||||
++$result['published'];
|
++$result['published'];
|
||||||
} else {
|
} else {
|
||||||
$publisher->save_as_draft( $final, $idea, $review, $run_id, $this->cost_tracker );
|
$publisher->save_as_draft( $final, $idea, $review, $run_id );
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$publisher->save_as_draft( $content, $idea, $review, $run_id, $this->cost_tracker );
|
$publisher->save_as_draft( $content, $idea, $review, $run_id );
|
||||||
++$result['rejected'];
|
++$result['rejected'];
|
||||||
PRAutoBlogger_Logger::instance()->info(
|
PRAutoBlogger_Logger::instance()->info(
|
||||||
'Article rejected by editor: ' . $idea->get_topic(),
|
'Article rejected by editor: ' . $idea->get_topic(),
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ PROMPT;
|
|||||||
|
|
||||||
$parsed = $this->rewrite_via_llm( $title, $first_para );
|
$parsed = $this->rewrite_via_llm( $title, $first_para );
|
||||||
|
|
||||||
$style_suffix = $this->get_style_suffix();
|
$style_suffix = get_option( 'prautoblogger_image_style_suffix', PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_SUFFIX );
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'prompt' => trim( $parsed['scene'] . ' ' . $style_suffix ),
|
'prompt' => trim( $parsed['scene'] . ' ' . $style_suffix ),
|
||||||
@@ -104,7 +104,7 @@ PROMPT;
|
|||||||
|
|
||||||
$parsed = $this->rewrite_via_llm( $title, $context );
|
$parsed = $this->rewrite_via_llm( $title, $context );
|
||||||
|
|
||||||
$style_suffix = $this->get_style_suffix();
|
$style_suffix = get_option( 'prautoblogger_image_style_suffix', PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_SUFFIX );
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'prompt' => trim( $parsed['scene'] . ' ' . $style_suffix ),
|
'prompt' => trim( $parsed['scene'] . ' ' . $style_suffix ),
|
||||||
@@ -269,7 +269,7 @@ PROMPT;
|
|||||||
*/
|
*/
|
||||||
public function build_fallback_prompt( string $title ): array {
|
public function build_fallback_prompt( string $title ): array {
|
||||||
$parsed = $this->synthesize_visual_concepts_fallback( $title, '' );
|
$parsed = $this->synthesize_visual_concepts_fallback( $title, '' );
|
||||||
$style_suffix = $this->get_style_suffix();
|
$style_suffix = get_option( 'prautoblogger_image_style_suffix', PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_SUFFIX );
|
||||||
return array(
|
return array(
|
||||||
'prompt' => trim( $parsed['scene'] . ' ' . $style_suffix ),
|
'prompt' => trim( $parsed['scene'] . ' ' . $style_suffix ),
|
||||||
'caption' => $parsed['caption'],
|
'caption' => $parsed['caption'],
|
||||||
@@ -311,20 +311,4 @@ PROMPT;
|
|||||||
$override = (string) get_option( 'prautoblogger_image_prompt_instructions', '' );
|
$override = (string) get_option( 'prautoblogger_image_prompt_instructions', '' );
|
||||||
return '' !== trim( $override ) ? $override : self::REWRITER_SYSTEM_PROMPT;
|
return '' !== trim( $override ) ? $override : self::REWRITER_SYSTEM_PROMPT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get image style suffix with empty-string safeguard.
|
|
||||||
*
|
|
||||||
* Treats both absent and empty string as "use default" to prevent
|
|
||||||
* silent style loss if the admin settings field is saved empty.
|
|
||||||
*
|
|
||||||
* @return string The style suffix with safe fallback.
|
|
||||||
*/
|
|
||||||
private function get_style_suffix(): string {
|
|
||||||
$style_suffix = (string) get_option( 'prautoblogger_image_style_suffix', '' );
|
|
||||||
if ( '' === trim( $style_suffix ) ) {
|
|
||||||
$style_suffix = PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_SUFFIX;
|
|
||||||
}
|
|
||||||
return $style_suffix;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,9 +235,8 @@ class PRAutoBlogger_Post_Assembler {
|
|||||||
* @param int $post_id Post ID.
|
* @param int $post_id Post ID.
|
||||||
* @param PRAutoBlogger_Article_Idea $idea Article idea (contains source IDs).
|
* @param PRAutoBlogger_Article_Idea $idea Article idea (contains source IDs).
|
||||||
* @param array $post_data Post data array (for article content).
|
* @param array $post_data Post data array (for article content).
|
||||||
* @param ?PRAutoBlogger_Cost_Tracker $cost_tracker Optional cost tracker for image generation cost logging.
|
|
||||||
*/
|
*/
|
||||||
public static function attach_generated_images( int $post_id, PRAutoBlogger_Article_Idea $idea, array $post_data, ?PRAutoBlogger_Cost_Tracker $cost_tracker = null ): void {
|
public static function attach_generated_images( int $post_id, PRAutoBlogger_Article_Idea $idea, array $post_data ): void {
|
||||||
// Fetch the original Reddit source data for Image B's source-driven prompt.
|
// Fetch the original Reddit source data for Image B's source-driven prompt.
|
||||||
// The idea's source_ids point to rows in the source_data table collected
|
// The idea's source_ids point to rows in the source_data table collected
|
||||||
// during the pipeline's collection step.
|
// during the pipeline's collection step.
|
||||||
@@ -248,7 +247,7 @@ class PRAutoBlogger_Post_Assembler {
|
|||||||
// internally, immediately after each image generates. This
|
// internally, immediately after each image generates. This
|
||||||
// ensures attachment persists even if the process times out
|
// ensures attachment persists even if the process times out
|
||||||
// before this method returns.
|
// before this method returns.
|
||||||
$result = ( new PRAutoBlogger_Image_Pipeline( null, $cost_tracker ) )->generate_and_attach_images( $post_id, $post_data, $source_data );
|
$result = ( new PRAutoBlogger_Image_Pipeline() )->generate_and_attach_images( $post_id, $post_data, $source_data );
|
||||||
|
|
||||||
if ( ! empty( $result['errors'] ) ) {
|
if ( ! empty( $result['errors'] ) ) {
|
||||||
foreach ( $result['errors'] as $error ) {
|
foreach ( $result['errors'] as $error ) {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ class PRAutoBlogger_Publisher {
|
|||||||
* @param PRAutoBlogger_Article_Idea $idea The original article idea.
|
* @param PRAutoBlogger_Article_Idea $idea The original article idea.
|
||||||
* @param PRAutoBlogger_Editorial_Review $review The editor's review.
|
* @param PRAutoBlogger_Editorial_Review $review The editor's review.
|
||||||
* @param string|null $run_id Pipeline run ID for log linking.
|
* @param string|null $run_id Pipeline run ID for log linking.
|
||||||
* @param ?PRAutoBlogger_Cost_Tracker $cost_tracker Optional cost tracker for image generation cost logging.
|
|
||||||
* @return int The created post ID.
|
* @return int The created post ID.
|
||||||
* @throws \RuntimeException If post creation fails.
|
* @throws \RuntimeException If post creation fails.
|
||||||
*/
|
*/
|
||||||
@@ -36,10 +35,9 @@ class PRAutoBlogger_Publisher {
|
|||||||
string $content,
|
string $content,
|
||||||
PRAutoBlogger_Article_Idea $idea,
|
PRAutoBlogger_Article_Idea $idea,
|
||||||
PRAutoBlogger_Editorial_Review $review,
|
PRAutoBlogger_Editorial_Review $review,
|
||||||
?string $run_id = null,
|
?string $run_id = null
|
||||||
?PRAutoBlogger_Cost_Tracker $cost_tracker = null
|
|
||||||
): int {
|
): int {
|
||||||
return $this->create_post( $content, $idea, $review, 'publish', $run_id, $cost_tracker );
|
return $this->create_post( $content, $idea, $review, 'publish', $run_id );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,7 +47,6 @@ class PRAutoBlogger_Publisher {
|
|||||||
* @param PRAutoBlogger_Article_Idea $idea The original article idea.
|
* @param PRAutoBlogger_Article_Idea $idea The original article idea.
|
||||||
* @param PRAutoBlogger_Editorial_Review $review The editor's review with rejection notes.
|
* @param PRAutoBlogger_Editorial_Review $review The editor's review with rejection notes.
|
||||||
* @param string|null $run_id Pipeline run ID for log linking.
|
* @param string|null $run_id Pipeline run ID for log linking.
|
||||||
* @param ?PRAutoBlogger_Cost_Tracker $cost_tracker Optional cost tracker for image generation cost logging.
|
|
||||||
* @return int The created post ID.
|
* @return int The created post ID.
|
||||||
* @throws \RuntimeException If post creation fails.
|
* @throws \RuntimeException If post creation fails.
|
||||||
*/
|
*/
|
||||||
@@ -57,10 +54,9 @@ class PRAutoBlogger_Publisher {
|
|||||||
string $content,
|
string $content,
|
||||||
PRAutoBlogger_Article_Idea $idea,
|
PRAutoBlogger_Article_Idea $idea,
|
||||||
PRAutoBlogger_Editorial_Review $review,
|
PRAutoBlogger_Editorial_Review $review,
|
||||||
?string $run_id = null,
|
?string $run_id = null
|
||||||
?PRAutoBlogger_Cost_Tracker $cost_tracker = null
|
|
||||||
): int {
|
): int {
|
||||||
return $this->create_post( $content, $idea, $review, 'draft', $run_id, $cost_tracker );
|
return $this->create_post( $content, $idea, $review, 'draft', $run_id );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,8 +75,7 @@ class PRAutoBlogger_Publisher {
|
|||||||
PRAutoBlogger_Article_Idea $idea,
|
PRAutoBlogger_Article_Idea $idea,
|
||||||
PRAutoBlogger_Editorial_Review $review,
|
PRAutoBlogger_Editorial_Review $review,
|
||||||
string $post_status,
|
string $post_status,
|
||||||
?string $run_id = null,
|
?string $run_id = null
|
||||||
?PRAutoBlogger_Cost_Tracker $cost_tracker = null
|
|
||||||
): int {
|
): int {
|
||||||
// Clean LLM artifacts, then inject peptide hyperlinks deterministically
|
// Clean LLM artifacts, then inject peptide hyperlinks deterministically
|
||||||
// before the content enters WordPress. Peptide linker no-ops gracefully
|
// before the content enters WordPress. Peptide linker no-ops gracefully
|
||||||
@@ -111,7 +106,7 @@ class PRAutoBlogger_Publisher {
|
|||||||
PRAutoBlogger_Post_Assembler::link_generation_logs( $post_id, $run_id );
|
PRAutoBlogger_Post_Assembler::link_generation_logs( $post_id, $run_id );
|
||||||
|
|
||||||
if ( 'publish' === $post_status ) {
|
if ( 'publish' === $post_status ) {
|
||||||
PRAutoBlogger_Post_Assembler::attach_generated_images( $post_id, $idea, $post_data, $cost_tracker );
|
PRAutoBlogger_Post_Assembler::attach_generated_images( $post_id, $idea, $post_data );
|
||||||
}
|
}
|
||||||
|
|
||||||
PRAutoBlogger_Logger::instance()->info(
|
PRAutoBlogger_Logger::instance()->info(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
* Plugin Name: PRAutoBlogger
|
* Plugin Name: PRAutoBlogger
|
||||||
* Plugin URI: https://peptiderepo.com/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.
|
* 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.12.3
|
* Version: 0.12.1
|
||||||
* Requires at least: 6.0
|
* Requires at least: 6.0
|
||||||
* Requires PHP: 7.4
|
* Requires PHP: 7.4
|
||||||
* Author: PeptideRepo
|
* Author: PeptideRepo
|
||||||
@@ -35,7 +35,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
| Defined here so every file in the plugin can reference paths, versions,
|
| Defined here so every file in the plugin can reference paths, versions,
|
||||||
| and limits without magic strings.
|
| and limits without magic strings.
|
||||||
*/
|
*/
|
||||||
define( 'PRAUTOBLOGGER_VERSION', '0.12.3' );
|
define( 'PRAUTOBLOGGER_VERSION', '0.12.1' );
|
||||||
define( 'PRAUTOBLOGGER_DB_VERSION', '1.1.0' );
|
define( 'PRAUTOBLOGGER_DB_VERSION', '1.1.0' );
|
||||||
define( 'PRAUTOBLOGGER_PLUGIN_FILE', __FILE__ );
|
define( 'PRAUTOBLOGGER_PLUGIN_FILE', __FILE__ );
|
||||||
define( 'PRAUTOBLOGGER_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
define( 'PRAUTOBLOGGER_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||||
|
|||||||
Reference in New Issue
Block a user