admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'abilities_bridge_nonce' ),
'user_id' => get_current_user_id(),
)
);
}
/**
* Render the main page
*/
public function render_page() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'abilities-bridge' ) );
}
// Check if consent has been given.
if ( ! Abilities_Bridge_Welcome_Wizard::is_setup_complete() ) {
wp_safe_redirect( admin_url( 'admin.php?page=abilities-bridge-welcome' ) );
exit;
}
// Check if API key is configured.
$provider_label = Abilities_Bridge_AI_Provider::get_provider_label();
$api_key_configured = Abilities_Bridge_AI_Provider::has_api_key();
?>
add your API key to use Abilities Bridge.', 'abilities-bridge' ),
esc_html( $provider_label ),
esc_url( admin_url( 'admin.php?page=abilities-bridge-settings' ) )
);
?>
__( 'Please complete the welcome wizard and provide consent before using Abilities Bridge.', 'abilities-bridge' ),
'redirect' => admin_url( 'admin.php?page=abilities-bridge-welcome' ),
)
);
}
// Register shutdown function to catch fatal errors.
register_shutdown_function(
function () {
$error = error_get_last();
if ( $error && in_array( $error['type'], array( E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR ) ) ) {
// Try to send JSON error response.
if ( ! headers_sent() ) {
wp_send_json_error(
array(
'message' => 'Fatal error: ' . $error['message'],
'file' => basename( $error['file'] ),
'line' => $error['line'],
)
);
}
}
}
);
check_ajax_referer( 'abilities_bridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
}
$message = isset( $_POST['message'] ) ? sanitize_textarea_field( wp_unslash( $_POST['message'] ) ) : '';
$conversation_id = isset( $_POST['conversation_id'] ) ? intval( wp_unslash( $_POST['conversation_id'] ) ) : null;
$plan_mode = isset( $_POST['plan_mode'] ) && sanitize_text_field( wp_unslash( $_POST['plan_mode'] ) ) === 'true';
if ( empty( $message ) ) {
wp_send_json_error( array( 'message' => 'Message cannot be empty' ) );
}
// Limit message length to prevent abuse.
if ( strlen( $message ) > 10000 ) {
wp_send_json_error( array( 'message' => 'Message too long (max 10,000 characters)' ) );
}
// Load or create conversation.
$conversation = new Abilities_Bridge_Conversation( $conversation_id );
if ( ! $conversation_id ) {
// Create new conversation with first message as title.
$title = substr( $message, 0, 80 ) . ( strlen( $message ) > 80 ? '...' : '' );
$new_id = $conversation->create( $title );
if ( ! $new_id ) {
wp_send_json_error( array( 'message' => 'Failed to create conversation' ) );
}
$conversation_id = $new_id;
}
// Log the action.
Abilities_Bridge_Logger::log_action( $conversation_id, 'sent message to AI' );
// Log initial activity.
Abilities_Bridge_Logger::log_tool_progress( $conversation_id, 'request', 'processing', '⏳ Processing message...' );
// Send message to Claude.
$result = $conversation->send_message( $message, $plan_mode );
if ( ! $result['success'] ) {
Abilities_Bridge_Logger::log_action( $conversation_id, 'message failed', $result['error'] );
$error_payload = array(
'message' => $result['error'],
'error_data' => isset( $result['error_data'] ) ? $result['error_data'] : null,
);
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
$error_payload['debug'] = $result;
}
wp_send_json_error( $error_payload );
}
// Check token usage.
$token_usage = $conversation->calculate_token_usage();
// Check for summary continuation thresholds.
$summary_status = 'none';
// Don't show warnings if this is a summary request.
$is_summary_request = ( strpos( $message, 'comprehensive summary' ) !== false );
if ( ! $is_summary_request ) {
if ( $token_usage['total'] >= 150000 ) {
$summary_status = 'final_warning'; // Second warning - last chance.
} elseif ( $token_usage['total'] >= 125000 ) {
$summary_status = 'first_warning'; // First warning - suggest summary.
}
}
// Truncate response if too large for JSON.
$response_data = array(
'response' => $result['response'],
'conversation_id' => $conversation_id,
'iterations' => $result['iterations'],
'tool_usage' => isset( $result['tool_usage'] ) ? $result['tool_usage'] : array(),
'token_usage' => $token_usage,
'summary_status' => $summary_status,
);
// Check response size and truncate if needed.
$json_size = strlen( wp_json_encode( $response_data ) );
if ( $json_size > 2097152 ) { // 2MB limit for JSON response.
$response_data['response'] = substr( $result['response'], 0, 500000 ) . "\n\n[Response truncated due to size]";
$response_data['truncated'] = true;
}
wp_send_json_success( $response_data );
}
/**
* AJAX: Create new conversation
*/
public function ajax_new_conversation() {
check_ajax_referer( 'abilities_bridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
}
// Check if consent has been given.
if ( ! Abilities_Bridge_Welcome_Wizard::is_setup_complete() ) {
wp_send_json_error(
array(
'message' => __( 'Please complete the welcome wizard and provide consent before using Abilities Bridge.', 'abilities-bridge' ),
'redirect' => admin_url( 'admin.php?page=abilities-bridge-welcome' ),
)
);
}
wp_send_json_success(
array(
'message' => 'New conversation started',
)
);
}
/**
* AJAX: Load conversation
*/
public function ajax_load_conversation() {
check_ajax_referer( 'abilities_bridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
}
// Check if consent has been given.
if ( ! Abilities_Bridge_Welcome_Wizard::is_setup_complete() ) {
wp_send_json_error(
array(
'message' => __( 'Please complete the welcome wizard and provide consent before using Abilities Bridge.', 'abilities-bridge' ),
'redirect' => admin_url( 'admin.php?page=abilities-bridge-welcome' ),
)
);
}
$conversation_id = isset( $_POST['conversation_id'] ) ? intval( wp_unslash( $_POST['conversation_id'] ) ) : 0;
if ( ! $conversation_id ) {
wp_send_json_error( array( 'message' => 'Invalid conversation ID' ) );
}
$conversation = Abilities_Bridge_Database::get_conversation( $conversation_id );
if ( ! $conversation ) {
wp_send_json_error( array( 'message' => 'Conversation not found' ) );
}
$messages = Abilities_Bridge_Database::get_messages( $conversation_id );
$formatted_messages = array();
foreach ( $messages as $msg ) {
$content = json_decode( $msg->content, true );
// Only include user and text assistant messages for display.
if ( 'user' === $msg->role ) {
// Check if this is a tool_result message (should be hidden from UI).
if ( is_array( $content ) && isset( $content[0]['type'] ) && 'tool_result' === $content[0]['type'] ) {
// Skip tool_result messages - these are internal Claude API messages.
continue;
}
if ( null === $content || is_string( $content ) ) {
// Plain text message - display it.
$formatted_messages[] = array(
'role' => 'user',
'content' => $msg->content,
'timestamp' => $msg->created_at,
);
} elseif ( is_array( $content ) ) {
// Array content - extract text, skip tool_result blocks.
$text_parts = array();
foreach ( $content as $block ) {
if ( isset( $block['type'] ) ) {
if ( 'text' === $block['type'] && isset( $block['text'] ) ) {
$text_parts[] = $block['text'];
}
// Skip tool_result blocks - they're not meant for display.
}
}
if ( ! empty( $text_parts ) ) {
$formatted_messages[] = array(
'role' => 'user',
'content' => implode( "\n\n", $text_parts ),
'timestamp' => $msg->created_at,
);
}
}
} elseif ( 'assistant' === $msg->role ) {
if ( is_array( $content ) ) {
// Extract text from content blocks.
$text_parts = array();
foreach ( $content as $block ) {
if ( isset( $block['type'] ) && 'text' === $block['type'] ) {
$text_parts[] = $block['text'];
}
}
if ( ! empty( $text_parts ) ) {
$formatted_messages[] = array(
'role' => 'assistant',
'content' => implode( "\n\n", $text_parts ),
'timestamp' => $msg->created_at,
);
}
} else {
$formatted_messages[] = array(
'role' => 'assistant',
'content' => $msg->content,
'timestamp' => $msg->created_at,
);
}
}
}
wp_send_json_success(
array(
'conversation' => $conversation,
'messages' => $formatted_messages,
)
);
}
/**
* AJAX: Delete conversation
*/
public function ajax_delete_conversation() {
check_ajax_referer( 'abilities_bridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
}
$conversation_id = isset( $_POST['conversation_id'] ) ? intval( wp_unslash( $_POST['conversation_id'] ) ) : 0;
if ( ! $conversation_id ) {
wp_send_json_error( array( 'message' => 'Invalid conversation ID' ) );
}
$result = Abilities_Bridge_Database::delete_conversation( $conversation_id );
if ( $result ) {
wp_send_json_success( array( 'message' => 'Conversation deleted' ) );
} else {
wp_send_json_error( array( 'message' => 'Failed to delete conversation' ) );
}
}
/**
* AJAX: Get conversations list
*/
public function ajax_get_conversations() {
check_ajax_referer( 'abilities_bridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
}
$user_id = get_current_user_id();
$conversations = Abilities_Bridge_Database::get_conversations( $user_id );
// Ensure conversations is always an array.
if ( ! is_array( $conversations ) ) {
$conversations = array();
}
wp_send_json_success(
array(
'conversations' => $conversations,
)
);
}
/**
* AJAX: Get token usage for current conversation
*/
public function ajax_get_token_usage() {
check_ajax_referer( 'abilities_bridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
}
$conversation_id = isset( $_POST['conversation_id'] ) ? intval( wp_unslash( $_POST['conversation_id'] ) ) : 0;
if ( $conversation_id ) {
$conversation = new Abilities_Bridge_Conversation( $conversation_id );
$token_usage = $conversation->calculate_token_usage();
wp_send_json_success( $token_usage );
} else {
// Return empty token usage for no conversation.
wp_send_json_success(
array(
'messages' => 0,
'system' => 0,
'tools' => 0,
'total' => 0,
'model' => 'claude-sonnet-4.5-20250929',
'input_limit' => 200000,
'output_limit' => 64000,
'percentage' => 0,
'status' => 'no_conversation',
)
);
}
}
/**
* AJAX: Get recent activity (for real-time progress polling)
*/
public function ajax_get_recent_activity() {
check_ajax_referer( 'abilities_bridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
}
// Close session to allow concurrent requests (enables real-time progress polling).
if ( session_status() === PHP_SESSION_ACTIVE ) {
session_write_close();
}
$conversation_id = isset( $_POST['conversation_id'] ) ? intval( wp_unslash( $_POST['conversation_id'] ) ) : 0;
if ( empty( $conversation_id ) ) {
wp_send_json_error( array( 'message' => 'Conversation ID required' ) );
}
$logs = Abilities_Bridge_Logger::get_recent_activity( $conversation_id, 30 );
$activities = array();
foreach ( $logs as $log ) {
$activities[] = array(
'message' => $log->action,
'function_name' => $log->function_name,
'timestamp' => $log->created_at,
);
}
wp_send_json_success( array( 'activities' => $activities ) );
}
/**
* AJAX: Get all conversation activity (for activity history dropdown)
*/
public function ajax_get_conversation_activity() {
check_ajax_referer( 'abilities_bridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
}
$conversation_id = isset( $_POST['conversation_id'] ) ? intval( wp_unslash( $_POST['conversation_id'] ) ) : 0;
if ( empty( $conversation_id ) ) {
wp_send_json_error( array( 'message' => 'Conversation ID required' ) );
}
$logs = Abilities_Bridge_Logger::get_conversation_activity( $conversation_id );
$activities = array();
foreach ( $logs as $log ) {
$input = $log->function_input ? json_decode( $log->function_input, true ) : null;
$activities[] = array(
'timestamp' => $log->created_at,
'function' => $log->function_name,
'description' => $log->action,
'input' => $input,
'success' => empty( $log->error_message ),
'error' => $log->error_message,
);
}
wp_send_json_success(
array(
'activities' => $activities,
'count' => count( $activities ),
)
);
}
/**
* AJAX: Set selected model
*/
public function ajax_set_model() {
check_ajax_referer( 'abilities_bridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
}
$model = isset( $_POST['model'] ) ? sanitize_text_field( wp_unslash( $_POST['model'] ) ) : '';
if ( empty( $model ) ) {
wp_send_json_error( array( 'message' => 'Model is required' ) );
}
$provider = Abilities_Bridge_AI_Provider::get_current_provider();
$success = Abilities_Bridge_AI_Provider::set_selected_model( $model, $provider );
if ( $success ) {
$available_models = Abilities_Bridge_AI_Provider::get_available_models( $provider );
wp_send_json_success(
array(
'message' => 'Model updated successfully',
'model' => $model,
'model_name' => isset( $available_models[ $model ] ) ? $available_models[ $model ] : $model,
)
);
} else {
wp_send_json_error(
array(
'message' => sprintf(
'Invalid model for %s provider',
Abilities_Bridge_AI_Provider::get_provider_label( $provider )
),
)
);
}
}
/**
* AJAX: Get selected model
*/
public function ajax_get_model() {
check_ajax_referer( 'abilities_bridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
}
$provider = Abilities_Bridge_AI_Provider::get_current_provider();
$model = Abilities_Bridge_AI_Provider::get_selected_model( $provider );
$available_models = Abilities_Bridge_AI_Provider::get_available_models( $provider );
wp_send_json_success(
array(
'provider' => $provider,
'model' => $model,
'model_name' => isset( $available_models[ $model ] ) ? $available_models[ $model ] : $model,
'available_models' => $available_models,
)
);
}
/**
* AJAX: Set selected provider
*/
public function ajax_set_provider() {
check_ajax_referer( 'abilities_bridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
}
$provider = isset( $_POST['provider'] ) ? sanitize_text_field( wp_unslash( $_POST['provider'] ) ) : '';
if ( empty( $provider ) ) {
wp_send_json_error( array( 'message' => 'Provider is required' ) );
}
$success = Abilities_Bridge_AI_Provider::set_selected_provider( $provider );
if ( $success ) {
$available_models = Abilities_Bridge_AI_Provider::get_available_models( $provider );
$model = Abilities_Bridge_AI_Provider::get_selected_model( $provider );
wp_send_json_success(
array(
'message' => 'Provider updated successfully',
'provider' => $provider,
'provider_label' => Abilities_Bridge_AI_Provider::get_provider_label( $provider ),
'model' => $model,
'model_name' => isset( $available_models[ $model ] ) ? $available_models[ $model ] : $model,
'available_models'=> $available_models,
)
);
} else {
wp_send_json_error( array( 'message' => 'Invalid provider' ) );
}
}
/**
* AJAX: Get selected provider
*/
public function ajax_get_provider() {
check_ajax_referer( 'abilities_bridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
}
$provider = Abilities_Bridge_AI_Provider::get_current_provider();
$available_models = Abilities_Bridge_AI_Provider::get_available_models( $provider );
$model = Abilities_Bridge_AI_Provider::get_selected_model( $provider );
wp_send_json_success(
array(
'provider' => $provider,
'provider_label' => Abilities_Bridge_AI_Provider::get_provider_label( $provider ),
'model' => $model,
'model_name' => isset( $available_models[ $model ] ) ? $available_models[ $model ] : $model,
'available_models' => $available_models,
)
);
}
/**
* AJAX: Create summary continuation conversation
*
* Creates a new conversation with the summary as the first user message,
* linking it to the parent conversation via parent_conversation_id.
*/
public function ajax_create_summary_continuation() {
check_ajax_referer( 'abilities_bridge_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
}
$parent_conversation_id = isset( $_POST['parent_conversation_id'] ) ? intval( wp_unslash( $_POST['parent_conversation_id'] ) ) : 0;
$summary_text = isset( $_POST['summary_text'] ) ? sanitize_textarea_field( wp_unslash( $_POST['summary_text'] ) ) : '';
if ( ! $parent_conversation_id ) {
wp_send_json_error( array( 'message' => 'Invalid parent conversation ID' ) );
}
if ( empty( $summary_text ) ) {
wp_send_json_error( array( 'message' => 'Summary text is required' ) );
}
// Get parent conversation details.
$parent_conversation = Abilities_Bridge_Database::get_conversation( $parent_conversation_id );
if ( ! $parent_conversation ) {
wp_send_json_error( array( 'message' => 'Parent conversation not found' ) );
}
// Create new conversation with same title and model.
global $wpdb;
$result = $wpdb->insert(
Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_CONVERSATIONS ),
array(
'user_id' => get_current_user_id(),
'title' => sanitize_text_field( $parent_conversation->title ), // Keep same title.
'provider' => ! empty( $parent_conversation->provider ) ? sanitize_text_field( $parent_conversation->provider ) : Abilities_Bridge_AI_Provider::get_current_provider(),
'model' => sanitize_text_field( $parent_conversation->model ),
'parent_conversation_id' => $parent_conversation_id,
),
array( '%d', '%s', '%s', '%s', '%d' )
);
if ( ! $result ) {
wp_send_json_error( array( 'message' => 'Failed to create new conversation' ) );
}
$new_conversation_id = $wpdb->insert_id;
// Add summary as first user message in new conversation.
Abilities_Bridge_Database::add_message(
$new_conversation_id,
'user',
$summary_text
);
wp_send_json_success(
array(
'new_conversation_id' => $new_conversation_id,
'parent_conversation_id' => $parent_conversation_id,
'message' => 'Summary continuation created successfully',
)
);
}
}