`, so a re-fire from `edit_comment` * doesn't double-spend tokens. * * Runs at priority 25 — AFTER the AI Copilot's own `priority 20` * hook in `includes/ai-copilot/hooks.php`. The Copilot's hook runs * for every install where any admin has AI enabled; we layer on top * to also fire when an anonymous commenter posts on a site whose * admin opted into Comments AI but didn't enable the Copilot more * broadly. * * @since 0.19.0 * * @param int $comment_id Newly-inserted comment id. */ function desktop_mode_comments_ai_on_new_comment( $comment_id ) { $comment_id = (int) $comment_id; if ( $comment_id <= 0 || ! desktop_mode_comments_ai_is_enabled() ) { return; } $comment = get_comment( $comment_id ); if ( ! $comment instanceof WP_Comment ) { return; } if ( '' !== $comment->comment_type && 'comment' !== $comment->comment_type ) { return; } if ( ! function_exists( 'desktop_mode_ai_resolve_user_id' ) || ! function_exists( 'desktop_mode_ai_is_enabled' ) || ! function_exists( 'desktop_mode_ai_schedule_job' ) ) { return; } $user_id = (int) $comment->user_id; if ( $user_id <= 0 || ! desktop_mode_ai_is_enabled( $user_id ) ) { $user_id = (int) desktop_mode_ai_resolve_user_id(); } if ( $user_id <= 0 || ! desktop_mode_ai_is_enabled( $user_id ) ) { return; } desktop_mode_ai_schedule_job( 'desktop_mode_ai_analyze_comment', array( $comment_id, $user_id ), 'comment_' . $comment_id ); } add_action( 'wp_insert_comment', 'desktop_mode_comments_ai_on_new_comment', 25, 1 ); /** * Fold the AI verdict into the per-row spam-confidence score. * * If a comment has been analyzed and the verdict came back with * `spam === true`, push the score firmly into the "high" tone * (≥ 70). `harmful === true` adds 20 — harmful but on-topic comments * deserve moderation attention even when they don't tip into spam. * * The `analyzed_at` field is intentionally ignored — we always trust * the latest verdict the AI produced, even if it's a few days old. * Re-analysis happens automatically on `edit_comment`, so the meta * stays fresh under normal moderation flows. * * @since 0.19.0 * * @param int $score Default heuristic score (0–100). * @param WP_Comment $comment Comment object. * @return int Adjusted score, clamped to 0–100. */ function desktop_mode_comments_ai_filter_spam_score( $score, $comment ) { if ( ! $comment instanceof WP_Comment ) { return $score; } if ( ! function_exists( 'desktop_mode_ai_get_meta' ) ) { return $score; } $meta = desktop_mode_ai_get_meta( 'comment', (int) $comment->comment_ID ); if ( ! is_array( $meta ) ) { return $score; } $score = (int) $score; if ( ! empty( $meta['spam'] ) ) { // Pin to high-tone territory. Heuristics may already have it // at 80; we just guarantee a floor. $score = max( $score, 75 ); } if ( ! empty( $meta['harmful'] ) ) { $score = min( 100, $score + 20 ); } return $score; } add_filter( 'desktop_mode_comments_window_spam_score', 'desktop_mode_comments_ai_filter_spam_score', 10, 2 ); /** * REST: GET / POST `desktop-mode/v1/comments/ai-settings`. * * Read returns `{ enabled: bool, providerConfigured: bool }`. * Write accepts `{ enabled: bool }`. Both `manage_options`-gated. * * `providerConfigured` is a convenience the UI uses to disable the * toggle with a "Configure an AI provider first" hint when no admin * on the site has wired up an API key. It's a UX cue — the toggle * stays writable even when it's `false` so an admin who's about to * configure the provider can flip this on first. * * @since 0.19.0 */ function desktop_mode_comments_ai_register_rest_route() { register_rest_route( 'desktop-mode/v1', '/comments/ai-settings', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => 'desktop_mode_comments_ai_rest_get', 'permission_callback' => static function () { return current_user_can( 'manage_options' ); }, ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => 'desktop_mode_comments_ai_rest_post', 'permission_callback' => static function () { return current_user_can( 'manage_options' ); }, 'args' => array( 'enabled' => array( 'required' => true, 'type' => 'boolean', ), ), ), ) ); } add_action( 'rest_api_init', 'desktop_mode_comments_ai_register_rest_route' ); /** * REST GET handler — returns the current state + provider hint. * * @since 0.19.0 * * @return WP_REST_Response */ function desktop_mode_comments_ai_rest_get() { return new WP_REST_Response( array( 'enabled' => desktop_mode_comments_ai_is_enabled(), 'providerConfigured' => desktop_mode_comments_ai_provider_configured(), ), 200 ); } /** * REST POST handler — updates the toggle. * * @since 0.19.0 * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ function desktop_mode_comments_ai_rest_post( WP_REST_Request $request ) { $enabled = (bool) $request['enabled']; update_option( DESKTOP_MODE_COMMENTS_AI_OPTION, $enabled, false ); /** * Fires after the Comments AI moderation toggle is changed. * * @since 0.19.0 * * @param bool $enabled New state. */ do_action( 'desktop_mode_comments_ai_toggled', $enabled ); return new WP_REST_Response( array( 'enabled' => $enabled, 'providerConfigured' => desktop_mode_comments_ai_provider_configured(), ), 200 ); } /** * Whether the site has at least one admin with AI configured. * * Cheap; reads the resolved-admin id and checks * `desktop_mode_ai_is_enabled` against it. Returns `false` when the * AI Copilot bundle isn't loaded. * * @since 0.19.0 * * @return bool */ function desktop_mode_comments_ai_provider_configured() { if ( ! function_exists( 'desktop_mode_ai_resolve_user_id' ) || ! function_exists( 'desktop_mode_ai_is_enabled' ) ) { return false; } $user_id = (int) desktop_mode_ai_resolve_user_id(); return $user_id > 0 && desktop_mode_ai_is_enabled( $user_id ); }