diff --git a/CHANGELOG.md b/CHANGELOG.md index fd61398..ae66ef6 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to PRAutoBlogger will be documented in this file. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project uses [Semantic Versioning](https://semver.org/). +## [0.4.7] — 2026-04-19 + +### Added +- **Cost breakdown popover.** The Cost column in the post list is now clickable. + Hovering or clicking shows a per-step breakdown: stage name, model used, token + count, and cost for every LLM call that produced the article. + ## [0.4.6] — 2026-04-19 ### Added diff --git a/includes/admin/class-post-list-columns.php b/includes/admin/class-post-list-columns.php index 67b2762..d7fea31 100644 --- a/includes/admin/class-post-list-columns.php +++ b/includes/admin/class-post-list-columns.php @@ -112,7 +112,19 @@ class PRAutoBlogger_Post_List_Columns { . '.wp-list-table .column-title{width:35%;word-wrap:break-word;overflow-wrap:break-word}' . '.column-prab_writing_model{width:100px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}' . '.column-prab_image_model{width:100px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}' - . '.column-prab_cost{width:55px;white-space:nowrap}' + . '.column-prab_cost{width:55px;white-space:nowrap;overflow:visible}' + . '.prab-cost-wrap{position:relative;display:inline-block}' + . '.prab-cost-trigger{font-size:12px;color:#2271b1;text-decoration:underline;cursor:pointer}' + . '.prab-cost-popover{display:none;position:absolute;right:0;top:100%;z-index:9999;' + . 'background:#fff;border:1px solid #c3c4c7;border-radius:4px;box-shadow:0 2px 8px rgba(0,0,0,.15);' + . 'padding:8px;min-width:340px;white-space:normal}' + . '.prab-cost-wrap:hover .prab-cost-popover,' + . '.prab-cost-trigger:focus+.prab-cost-popover{display:block}' + . '.prab-cost-table{width:100%;border-collapse:collapse;font-size:12px}' + . '.prab-cost-table th{text-align:left;border-bottom:2px solid #dcdcde;padding:3px 6px;color:#50575e;font-weight:600}' + . '.prab-cost-table td{padding:3px 6px;border-bottom:1px solid #f0f0f1;color:#3c434a}' + . '.prab-cost-table tr:last-child td{border-bottom:none}' + . '.prab-cost-total td{border-top:2px solid #dcdcde;border-bottom:none}' . ''; } @@ -151,12 +163,11 @@ class PRAutoBlogger_Post_List_Columns { } /** - * Sum all generation_log costs for this post and render. + * Fetch generation_log rows for a post, render total with clickable breakdown. * * @param int $post_id The post ID. */ private function render_cost_cell( int $post_id ): void { - // Only query for posts we generated — skip the DB hit for regular posts. $is_generated = get_post_meta( $post_id, '_prautoblogger_generated', true ); if ( '1' !== $is_generated ) { echo '—'; @@ -167,29 +178,76 @@ class PRAutoBlogger_Post_List_Columns { $table = $wpdb->prefix . 'prautoblogger_generation_log'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching - $total = (float) $wpdb->get_var( + $rows = $wpdb->get_results( $wpdb->prepare( - "SELECT SUM(estimated_cost) FROM {$table} WHERE post_id = %d", + "SELECT stage, model, prompt_tokens, completion_tokens, estimated_cost + FROM {$table} WHERE post_id = %d ORDER BY id ASC", $post_id - ) + ), + ARRAY_A ); - if ( $total <= 0.0 ) { + if ( empty( $rows ) ) { echo '$0.00'; return; } - // Show 4 decimals for sub-cent precision, 2 decimals for larger amounts. + $total = array_sum( array_column( $rows, 'estimated_cost' ) ); $formatted = $total < 0.01 ? sprintf( '$%.4f', $total ) : sprintf( '$%.2f', $total ); + $breakdown = $this->build_breakdown_html( $rows, $total ); + printf( - '%s', - esc_html( $formatted ) + '' + . '%s' + . '%s' + . '', + esc_html( $formatted ), + $breakdown ); } + /** + * Build the HTML breakdown table for the cost popover. + * + * @param array[] $rows Generation log rows. + * @param float $total Total cost. + * @return string HTML table (already escaped). + */ + private function build_breakdown_html( array $rows, float $total ): string { + $html = ''; + $html .= ''; + + foreach ( $rows as $row ) { + $stage = esc_html( ucfirst( str_replace( '_', ' ', $row['stage'] ) ) ); + $model = esc_html( $this->shorten( $row['model'] ) ); + $tokens = (int) $row['prompt_tokens'] + (int) $row['completion_tokens']; + $cost = (float) $row['estimated_cost']; + $cost_f = $cost < 0.01 ? sprintf( '$%.4f', $cost ) : sprintf( '$%.2f', $cost ); + + $html .= sprintf( + '', + $stage, + esc_attr( $row['model'] ), + $model, + esc_html( number_format( $tokens ) ), + esc_html( $cost_f ) + ); + } + + $total_f = $total < 0.01 ? sprintf( '$%.4f', $total ) : sprintf( '$%.2f', $total ); + $html .= sprintf( + '' + . '', + esc_html( $total_f ) + ); + $html .= '
StageModelTokensCost
%s%s%s%s
Total%s
'; + + return $html; + } + /** * Strip provider prefix for compact display. * diff --git a/prautoblogger.php b/prautoblogger.php index b730aee..04210dc 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.4.6 + * Version: 0.4.7 * 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.4.6' ); +define( 'PRAUTOBLOGGER_VERSION', '0.4.7' ); define( 'PRAUTOBLOGGER_DB_VERSION', '1.1.0' ); define( 'PRAUTOBLOGGER_PLUGIN_FILE', __FILE__ ); define( 'PRAUTOBLOGGER_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );