' . esc_html__( 'Token recovered!', 'ai-search' ) . ' ' . esc_html__( 'A fresh API token was issued for this site. Your existing usage limits and quotas were preserved.', 'ai-search' ) . '
';
} elseif ( $recovery_status === 'failed' ) {
echo '' . esc_html__( 'Token recovery failed.', 'ai-search' ) . ' ' . esc_html__( 'Could not issue a new token. Please try again in a few minutes or contact support.', 'ai-search' ) . '
';
}
}
// Handle connection check messages
if ( isset( $_GET['token_validation'] ) ) {
$validation_status = sanitize_text_field( $_GET['token_validation'] );
if ( $validation_status === 'success' ) {
$validation_data = get_transient( 'ai_search_validation_data' );
if ( $validation_data ) {
echo '' . esc_html__( 'Connection successful!', 'ai-search' ) . ' ' . esc_html__( 'AI Search Service is working correctly.', 'ai-search' ) . '
';
if ( isset( $validation_data['client'] ) ) {
$client = $validation_data['client'];
$used_total = intval( $client['used_total'] ?? 0 );
$total_limit = intval( $client['total_limit'] ?? 10000 );
$remaining = $total_limit - $used_total;
echo '
' . sprintf( esc_html__( 'Embeddings used: %1$s / %2$s (%3$s remaining)', 'ai-search' ), '' . esc_html( number_format( $used_total ) ) . '', esc_html( number_format( $total_limit ) ), esc_html( number_format( $remaining ) ) ) . '
';
}
echo '
';
delete_transient( 'ai_search_validation_data' );
}
} elseif ( $validation_status === 'invalid' ) {
$error_data = get_transient( 'ai_search_validation_error' );
echo '' . esc_html__( 'Connection failed!', 'ai-search' ) . '
';
if ( $error_data && isset( $error_data['details'] ) ) {
echo '
' . esc_html( $error_data['details'] ) . '
';
}
// The service returns error: "Invalid token" specifically when the
// token isn't found in its DB (vs. "Origin mismatch" which has its
// own dedicated reconnect flow). Offer one-click recovery only for
// that case so we don't show a misleading button for other errors.
$is_invalid_token = $error_data && isset( $error_data['error'] ) && $error_data['error'] === 'Invalid token';
if ( $is_invalid_token ) {
$recover_url = wp_nonce_url(
admin_url( 'admin-post.php?action=ai_search_recover_token' ),
'ai_search_recover_token'
);
echo '
' . esc_html__( 'Your token is no longer recognized by the service. You can reconnect by issuing a fresh token — your usage limits and quotas will be preserved.', 'ai-search' ) . '
';
echo '
' . esc_html__( 'Reconnect with new token', 'ai-search' ) . '
';
}
echo '
';
delete_transient( 'ai_search_validation_error' );
} elseif ( $validation_status === 'error' ) {
echo '' . esc_html__( 'Connection failed!', 'ai-search' ) . ' ' . esc_html__( 'Unable to reach AI Search Service. Please try again later.', 'ai-search' ) . '
';
} elseif ( $validation_status === 'no_token' ) {
echo '' . esc_html__( 'Not registered.', 'ai-search' ) . ' ' . esc_html__( 'Please save your settings to register with the AI Search Service.', 'ai-search' ) . '
';
}
}
// Subscription panel + activation form moved to a dedicated Upgrade /
// Subscription tab in v1.37.0. Badge visibility + tooltip customisation
// moved to a dedicated Appearance tab in v1.37.0.
// General now hosts only the notices + service status.
// Blocks shipped in 1.33.0 and are entirely opt-in, which means a store
// can update and never discover the headline feature. Announce it once,
// dismissible, and only while it has not been dismissed.
//
// v1.35.0 auto-dismiss: if the site already has an AI Search block
// somewhere (post, page, template, pattern - any post_type with the
// block marker in post_content), the user found the feature on their
// own and the promo card is just noise. Mark it dismissed and never
// render it again. Cheap LIKE query with LIMIT 1, runs once per view.
if ( ! get_option( 'ai_search_blocks_notice_dismissed', false ) ) {
global $wpdb;
$ais_block_used = $wpdb->get_var(
"SELECT 1 FROM {$wpdb->posts}
WHERE post_status IN ( 'publish', 'draft', 'inherit' )
AND ( post_content LIKE '%wp:ai-search/search-box%'
OR post_content LIKE '%wp:ai-search/search-button%' )
LIMIT 1"
);
if ( $ais_block_used ) {
update_option( 'ai_search_blocks_notice_dismissed', true );
}
}
// v1.44.0: Smart Matching spotlight on the General tab. Uses the same
// --highlight panel style as the blocks announcement next to it so the
// two sit as peers in the admin's own visual language — no gradient
// shell, no shouting. Hidden once Smart Matching is already active.
$sm_already_on = AI_Search::is_smart_embeddings_enabled();
$sm_locked = ! AI_Search::plan_meets( 'standard' );
if ( ! $sm_already_on ) :
$sm_cta_url = $sm_locked
? 'https://wp-search.ai/pricing/?utm_source=plugin&utm_medium=spotlight&utm_campaign=smart_matching'
: admin_url( 'admin.php?page=ai-search&tab=embeddings#ai_search_smart_embeddings' );
$sm_cta_label = $sm_locked
? __( 'See plans', 'ai-search' )
: __( 'Enable Smart Matching', 'ai-search' );
?>