$columns Existing columns. * @return array Modified columns. */ public function filter_add_columns( array $columns ): array { $new = array(); foreach ( $columns as $key => $label ) { $new[ $key ] = $label; if ( 'title' === $key ) { $new['prab_writing_model'] = __( 'Writing Model', 'prautoblogger' ); $new['prab_image_model'] = __( 'Image Model', 'prautoblogger' ); $new['prab_cost'] = __( 'Cost', 'prautoblogger' ); } } return $new; } /** * Render cell content for our custom columns. * * @param string $column Column identifier. * @param int $post_id Current post ID. */ public function on_render_column( string $column, int $post_id ): void { switch ( $column ) { case 'prab_writing_model': $this->render_model_cell( (string) get_post_meta( $post_id, '_prautoblogger_model_used', true ) ); break; case 'prab_image_model': $this->render_image_model_cell( $post_id ); break; case 'prab_cost': $this->render_cost_cell( $post_id ); break; } } /** * Make the writing model column sortable. * * @param array $columns Sortable column map. * @return array */ public function filter_sortable_columns( array $columns ): array { $columns['prab_writing_model'] = 'prab_writing_model'; return $columns; } /** * Handle sorting by writing model meta value. * * @param \WP_Query $query The admin posts query. */ public function on_sort_by_model( \WP_Query $query ): void { if ( ! is_admin() || ! $query->is_main_query() ) { return; } if ( 'prab_writing_model' !== $query->get( 'orderby' ) ) { return; } $query->set( 'meta_key', '_prautoblogger_model_used' ); $query->set( 'orderby', 'meta_value' ); } /** * Output column width styles on the Posts list screen only. */ public function on_column_styles(): void { $screen = get_current_screen(); if ( ! $screen || 'edit-post' !== $screen->id ) { return; } echo ''; } // ── Private helpers ───────────────────────────────────────────────── /** * Render a model name cell with shortened display and full tooltip. * * @param string $model Full model identifier (may be empty). */ private function render_model_cell( string $model ): void { if ( '' === $model ) { echo '—'; return; } printf( '%s', esc_attr( $model ), esc_html( $this->shorten( $model ) ) ); } /** * Look up the featured image's model and render it. * * @param int $post_id The post ID. */ private function render_image_model_cell( int $post_id ): void { $thumbnail_id = (int) get_post_thumbnail_id( $post_id ); if ( 0 === $thumbnail_id ) { echo '—'; return; } $model = (string) get_post_meta( $thumbnail_id, '_prautoblogger_image_model', true ); $this->render_model_cell( $model ); } /** * 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 { $is_generated = get_post_meta( $post_id, '_prautoblogger_generated', true ); if ( '1' !== $is_generated ) { echo '—'; return; } global $wpdb; $table = $wpdb->prefix . 'prautoblogger_generation_log'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $rows = $wpdb->get_results( $wpdb->prepare( "SELECT stage, model, prompt_tokens, completion_tokens, estimated_cost FROM {$table} WHERE post_id = %d ORDER BY id ASC", $post_id ), ARRAY_A ); if ( empty( $rows ) ) { echo '$0.00'; return; } $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' . '%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. * * @param string $model Full model identifier. * @return string Shortened name. */ private function shorten( string $model ): string { $pos = strpos( $model, '/' ); return false !== $pos ? substr( $model, $pos + 1 ) : $model; } }