settings->is_enabled() ) { return; } $threshold = (int) $this->settings->get( 'alert_threshold_pct', 80 ); $messages = array(); // Check global daily. $daily_pct = $this->usage_tracker->global_daily_pct(); if ( $daily_pct >= 100 ) { $messages[] = sprintf( /* translators: %s: percentage */ __( 'AI Valve: Global daily token budget exceeded (%s%% used). New AI requests are being blocked.', 'ai-valve' ), number_format_i18n( (int) $daily_pct ) ); $this->maybe_send_email( 'daily', $daily_pct ); } elseif ( $daily_pct >= $threshold ) { $messages[] = sprintf( /* translators: %s: percentage */ __( 'AI Valve: Global daily token usage at %s%% of budget.', 'ai-valve' ), number_format_i18n( (int) $daily_pct ) ); } // Check global monthly. $monthly_pct = $this->usage_tracker->global_monthly_pct(); if ( $monthly_pct >= 100 ) { $messages[] = sprintf( /* translators: %s: percentage */ __( 'AI Valve: Global monthly token budget exceeded (%s%% used). New AI requests are being blocked.', 'ai-valve' ), number_format_i18n( (int) $monthly_pct ) ); $this->maybe_send_email( 'monthly', $monthly_pct ); } elseif ( $monthly_pct >= $threshold ) { $messages[] = sprintf( /* translators: %s: percentage */ __( 'AI Valve: Global monthly token usage at %s%% of budget.', 'ai-valve' ), number_format_i18n( (int) $monthly_pct ) ); } // Check per-plugin budgets. $plugin_budgets = (array) $this->settings->get( 'plugin_budgets', array() ); foreach ( $plugin_budgets as $slug => $limits ) { $slug = (string) $slug; $p_daily_pct = $this->usage_tracker->plugin_daily_pct( $slug ); if ( $p_daily_pct >= 100 ) { $messages[] = sprintf( /* translators: 1: plugin slug 2: percentage */ __( 'AI Valve: %1$s daily token budget exceeded (%2$s%% used).', 'ai-valve' ), '' . esc_html( $slug ) . '', number_format_i18n( (int) $p_daily_pct ) ); } elseif ( $p_daily_pct >= $threshold ) { $messages[] = sprintf( /* translators: 1: plugin slug 2: percentage */ __( 'AI Valve: %1$s daily token usage at %2$s%% of budget.', 'ai-valve' ), '' . esc_html( $slug ) . '', number_format_i18n( (int) $p_daily_pct ) ); } $p_monthly_pct = $this->usage_tracker->plugin_monthly_pct( $slug ); if ( $p_monthly_pct >= 100 ) { $messages[] = sprintf( /* translators: 1: plugin slug 2: percentage */ __( 'AI Valve: %1$s monthly token budget exceeded (%2$s%% used).', 'ai-valve' ), '' . esc_html( $slug ) . '', number_format_i18n( (int) $p_monthly_pct ) ); } elseif ( $p_monthly_pct >= $threshold ) { $messages[] = sprintf( /* translators: 1: plugin slug 2: percentage */ __( 'AI Valve: %1$s monthly token usage at %2$s%% of budget.', 'ai-valve' ), '' . esc_html( $slug ) . '', number_format_i18n( (int) $p_monthly_pct ) ); } } foreach ( $messages as $msg ) { $type = str_contains( $msg, 'exceeded' ) ? 'error' : 'warning'; printf( '

%s

', esc_attr( $type ), wp_kses( $msg, array( 'strong' => array() ) ) ); } } /* ------------------------------------------------------------------ * Dashboard widget * ----------------------------------------------------------------*/ public function register_dashboard_widget(): void { if ( ! current_user_can( 'manage_options' ) ) { return; } wp_add_dashboard_widget( 'soderlind_aivalve_usage_widget', __( 'AI Valve — AI Token Usage', 'ai-valve' ), array( $this, 'render_dashboard_widget' ), ); } public function render_dashboard_widget(): void { $daily = $this->usage_tracker->global_tokens_today(); $monthly = $this->usage_tracker->global_tokens_this_month(); $daily_limit = (int) $this->settings->get( 'global_daily_limit', 0 ); $monthly_limit = (int) $this->settings->get( 'global_monthly_limit', 0 ); echo ''; // Daily row. echo ''; // Monthly row. echo ''; echo '
' . esc_html__( 'Today', 'ai-valve' ) . ''; echo esc_html( number_format_i18n( $daily ) ); if ( $daily_limit > 0 ) { echo ' / ' . esc_html( number_format_i18n( $daily_limit ) ); $this->render_progress_bar( $daily, $daily_limit ); } echo '
' . esc_html__( 'This month', 'ai-valve' ) . ''; echo esc_html( number_format_i18n( $monthly ) ); if ( $monthly_limit > 0 ) { echo ' / ' . esc_html( number_format_i18n( $monthly_limit ) ); $this->render_progress_bar( $monthly, $monthly_limit ); } echo '
'; printf( '

%s

', esc_url( admin_url( 'options-general.php?page=soderlind-aivalve' ) ), esc_html__( 'View full dashboard →', 'ai-valve' ) ); } /* ------------------------------------------------------------------ * Helpers * ----------------------------------------------------------------*/ private function render_progress_bar( int $used, int $limit ): void { $pct = min( 100, (int) ( ( $used / max( 1, $limit ) ) * 100 ) ); $color = match ( true ) { $pct >= 100 => '#d63638', $pct >= 80 => '#dba617', default => '#00a32a', }; printf( '
' . '
' . '
', esc_attr( $color ), esc_attr( (string) absint( $pct ) ) ); } /** * Sends a one-time email per calendar day per period type. */ private function maybe_send_email( string $period, float $pct ): void { $email = $this->settings->get( 'alert_email', '' ); if ( empty( $email ) || ! is_email( $email ) ) { return; } $transient_key = self::EMAIL_TRANSIENT_PREFIX . $period . '_' . gmdate( 'Y-m-d' ); if ( get_transient( $transient_key ) ) { return; // Already sent today. } $subject = sprintf( /* translators: 1: period (daily/monthly), 2: percentage */ __( '[AI Valve] %1$s token budget exceeded (%2$s%%)', 'ai-valve' ), ucfirst( $period ), number_format_i18n( (int) $pct ) ); $body = sprintf( /* translators: 1: site name, 2: period, 3: percentage, 4: dashboard URL */ __( "Site: %1\$s\nPeriod: %2\$s\nUsage: %3\$s%% of budget\n\nView dashboard: %4\$s", 'ai-valve' ), get_bloginfo( 'name' ), $period, number_format_i18n( (int) $pct ), admin_url( 'options-general.php?page=soderlind-aivalve' ) ); wp_mail( $email, $subject, $body ); set_transient( $transient_key, 1, DAY_IN_SECONDS ); } }