bool, 'status' => int, 'data' => array|null, 'raw' => string, 'error' => string ). */ function rubik_chat_api_request( $method, $path, $body = null, $timeout = 60 ) { $token = get_option( 'rubik_token', '' ); $headers = array( 'Accept' => 'application/json' ); if ( $token !== '' ) { $headers['Authorization'] = 'Bearer ' . $token; } $args = array( 'method' => strtoupper( $method ), 'timeout' => $timeout, 'headers' => $headers, ); if ( $body !== null ) { $args['headers']['Content-Type'] = 'application/json'; $args['body'] = wp_json_encode( $body ); } $url = RUBIK_CHAT_API_BASE . $path; $response = wp_remote_request( $url, $args ); rubik_chat_log_api_call( $url, $args['method'], $args, $response ); if ( is_wp_error( $response ) ) { return array( 'ok' => false, 'status' => 0, 'data' => null, 'raw' => '', 'error' => $response->get_error_message() ); } $status = (int) wp_remote_retrieve_response_code( $response ); $raw = (string) wp_remote_retrieve_body( $response ); $data = json_decode( $raw, true ); $ok = $status >= 200 && $status < 300; $error = ''; if ( ! $ok ) { $error = is_array( $data ) ? ( $data['message'] ?? $data['error'] ?? '' ) : ''; if ( is_array( $data ) && ! empty( $data['errors'] ) && is_array( $data['errors'] ) ) { $parts = array(); foreach ( $data['errors'] as $messages ) { $parts[] = is_array( $messages ) ? implode( ', ', $messages ) : (string) $messages; } $error = implode( ' | ', $parts ); } if ( ! is_string( $error ) || $error === '' ) { $error = 'RubikChat API error (HTTP ' . $status . ').'; } if ( $status === 401 ) { $error = 'Your RubikChat session has expired. Please disconnect and connect again.'; } } return array( 'ok' => $ok, 'status' => $status, 'data' => is_array( $data ) ? $data : null, 'raw' => $raw, 'error' => $error ); } /** The organization slug (organization.url) used in API paths. */ function rubik_chat_org_slug() { $org_slug = trim( (string) get_option( 'rubik_organization_url', '' ) ); if ( preg_match( '#^https?://#i', $org_slug ) ) { $parsed = wp_parse_url( $org_slug ); $org_slug = ! empty( $parsed['host'] ) ? $parsed['host'] : $org_slug; } return $org_slug; } /** Agent instructions; page content goes to the knowledge base, not here. */ function rubik_chat_default_instructions( $custom_instructions = '' ) { $custom_instructions = trim( (string) $custom_instructions ); if ( $custom_instructions !== '' ) { return $custom_instructions; } $site_name = trim( (string) get_bloginfo( 'name' ) ); if ( $site_name === '' ) { $site_name = 'this website'; } return "You are the official AI assistant for {$site_name}.\n" . "- Answer questions using the website knowledge base you have been given.\n" . "- Be friendly, clear and concise. Use bullet points for lists.\n" . "- If you don't know the answer, say so and offer to take the visitor's contact details.\n" . "- Reply in the language the visitor writes in."; } /** One page as a text knowledge source: title and URL for context, then the page text. */ function rubik_chat_page_source_text( $page_id ) { $content = get_post_field( 'post_content', $page_id ); $content = wp_strip_all_tags( strip_shortcodes( (string) $content ) ); $content = html_entity_decode( $content, ENT_QUOTES, 'UTF-8' ); $content = trim( preg_replace( '/\s+/u', ' ', $content ) ); if ( $content === '' ) { return ''; } $text = 'Page: ' . get_the_title( $page_id ) . "\nURL: " . get_permalink( $page_id ) . "\n\n" . $content; // Keep each source a reasonable size return function_exists( 'mb_substr' ) ? mb_substr( $text, 0, 60000, 'UTF-8' ) : substr( $text, 0, 60000 ); } /** * The knowledge base the plugin adds pages to: the stored one, else the agent's first one, * else a new "{name} Knowledge Base" (attached to the agent when there is one). */ function rubik_chat_ensure_knowledge_base( $bot_name ) { $kb_id = absint( get_option( 'rubik_kb_id', 0 ) ); if ( $kb_id ) { return array( 'ok' => true, 'id' => $kb_id ); } $org_slug = rubik_chat_org_slug(); $bot_id = get_option( 'rubik_bot_id', '' ); $existing = array(); if ( $bot_id !== '' ) { $agent = rubik_chat_api_request( 'GET', '/agents/get/' . rawurlencode( $bot_id ) ); if ( $agent['ok'] && is_array( $agent['data'] ) ) { $agent_data = $agent['data']['agent'] ?? $agent['data']['data'] ?? $agent['data']; $kbs = $agent_data['knowledge_bases'] ?? $agent_data['knowledgeBases'] ?? array(); foreach ( (array) $kbs as $kb ) { if ( ! empty( $kb['id'] ) ) { $existing[] = (int) $kb['id']; } } } if ( ! empty( $existing ) ) { update_option( 'rubik_kb_id', $existing[0] ); return array( 'ok' => true, 'id' => $existing[0] ); } } $name = trim( (string) $bot_name ) !== '' ? trim( (string) $bot_name ) : get_bloginfo( 'name' ); $created = rubik_chat_api_request( 'POST', '/organizations/' . rawurlencode( $org_slug ) . '/knowledge-bases', array( 'name' => $name . ' Knowledge Base', 'provider' => 'open_ai', ) ); $kb_id = absint( $created['data']['data']['id'] ?? 0 ); if ( ! $created['ok'] || ! $kb_id ) { return array( 'ok' => false, 'error' => 'Could not create the knowledge base: ' . ( $created['error'] ?: 'no id returned' ) ); } update_option( 'rubik_kb_id', $kb_id ); // An existing agent: attach the new knowledge base (keeping any others it has) if ( $bot_id !== '' ) { $attach = rubik_chat_api_request( 'PUT', '/agents/' . rawurlencode( $bot_id ) . '/knowledge-bases', array( 'knowledge_base_ids' => array_values( array_unique( array_merge( $existing, array( $kb_id ) ) ) ), ) ); if ( ! $attach['ok'] ) { return array( 'ok' => false, 'error' => 'Could not attach the knowledge base to the agent: ' . $attach['error'] ); } } return array( 'ok' => true, 'id' => $kb_id ); } /** * Makes the knowledge base match the selected pages: updates pages already added, adds new * ones and removes pages that are no longer selected. Training runs on RubikChat. */ function rubik_chat_sync_page_sources( $kb_id, $page_ids ) { $base = '/organizations/' . rawurlencode( rubik_chat_org_slug() ) . '/knowledge-bases/' . absint( $kb_id ) . '/sources'; $map = get_option( 'rubik_page_sources', array() ); $map = is_array( $map ) ? $map : array(); $result = array( 'added' => 0, 'updated' => 0, 'removed' => 0, 'errors' => array() ); $wanted = array(); foreach ( $page_ids as $page_id ) { $page_id = absint( $page_id ); $text = $page_id ? rubik_chat_page_source_text( $page_id ) : ''; if ( $text === '' ) { continue; } $wanted[ $page_id ] = true; if ( ! empty( $map[ $page_id ] ) ) { $updated = rubik_chat_api_request( 'PUT', $base . '/' . absint( $map[ $page_id ] ), array( 'text' => $text ) ); if ( $updated['ok'] ) { $result['updated']++; continue; } if ( $updated['status'] !== 404 ) { $result['errors'][] = get_the_title( $page_id ) . ': ' . $updated['error']; continue; } // Removed on RubikChat: add it again unset( $map[ $page_id ] ); } $added = rubik_chat_api_request( 'POST', $base, array( 'source_type' => 'text', 'text' => $text ) ); $source_id = absint( $added['data']['data']['id'] ?? 0 ); if ( $added['ok'] && $source_id ) { $map[ $page_id ] = $source_id; $result['added']++; } else { $result['errors'][] = get_the_title( $page_id ) . ': ' . ( $added['error'] ?: 'no source id returned' ); } } foreach ( $map as $page_id => $source_id ) { if ( isset( $wanted[ $page_id ] ) ) { continue; } $deleted = rubik_chat_api_request( 'DELETE', $base . '/' . absint( $source_id ) ); if ( $deleted['ok'] || $deleted['status'] === 404 ) { unset( $map[ $page_id ] ); $result['removed']++; } else { $result['errors'][] = 'Page #' . $page_id . ': ' . $deleted['error']; } } update_option( 'rubik_page_sources', $map ); return $result; } function rubik_chat_add_menu() { add_menu_page( __( 'Welcome to RubikChat', 'rubikchat' ), __( 'RubikChat', 'rubikchat' ), 'manage_options', 'rubik-chat', 'rubik_chat_settings', rubik_chat_menu_icon(), 80 ); add_submenu_page( 'rubik-chat', __( 'Settings', 'rubikchat' ), __( 'Settings', 'rubikchat' ), 'manage_options', 'rubik-chat', 'rubik_chat_settings' ); $agent_type = get_option( 'rubik_agent_type', '' ); if ( $agent_type === 'agent_customize' ) { add_submenu_page( 'rubik-chat', __( 'Logs', 'rubikchat' ), __( 'Logs', 'rubikchat' ), 'manage_options', 'rubik-chat-logs', 'rubik_chat_logs_page' ); } } add_action( 'admin_menu', 'rubik_chat_add_menu' ); /** * Safe no-op: prevents fatal if an old install hooks this on admin_footer. */ function rubik_chat_admin_styles( $hook ) { if ( strpos( $hook, 'rubik-chat' ) === false ) { return; } $css_file = plugin_dir_path( __FILE__ ) . 'assets/styles.css'; if ( file_exists( $css_file ) ) { wp_enqueue_style( 'rubik-chat-admin-styles', plugins_url( 'assets/styles.css', __FILE__ ), array(), filemtime( $css_file ) ); } // API Logs page: show/hide details, confirm clearing if ( strpos( $hook, 'rubik-chat-logs' ) !== false ) { wp_enqueue_script( 'rubik-chat-logs', plugins_url( 'assets/logs.js', __FILE__ ), array(), RUBIKCHAT_VERSION, true ); return; } if ( $hook !== 'toplevel_page_rubik-chat' ) { return; } // A clean settings screen: no admin footer or other plugins' notices wp_add_inline_style( 'rubik-chat-admin-styles', '#wpfooter, #wpbody-content > .notice, .update-nag { display: none !important; } #wpbody-content { padding-bottom: 24px !important; } .rubik-chat-wrap { margin-bottom: 12px !important; }' ); wp_enqueue_script( 'rubik-chat-admin', plugins_url( 'assets/admin.js', __FILE__ ), array(), RUBIKCHAT_VERSION, true ); wp_localize_script( 'rubik-chat-admin', 'RubikChatAdmin', array( 'nonce' => wp_create_nonce( 'rubik_chat_agent_nonce' ), ) ); // Live test on the settings page: the same RubikChat widget as the website $bot_id = get_option( 'rubik_bot_id', '' ); if ( $bot_id !== '' && get_option( 'rubik_connected', 'no' ) === 'yes' ) { wp_enqueue_script( 'rubik-chatbot-widget', RUBIK_CHAT_WIDGET_SCRIPT, array(), RUBIKCHAT_VERSION, true ); wp_add_inline_script( 'rubik-chatbot-widget', 'window.embeddedChatbotConfig = ' . wp_json_encode( array( 'chatbotId' => (string) $bot_id, 'domain' => 'widget.rubikchat.com' ) ) . ';', 'before' ); } } /** The admin menu icon: the RubikChat mark as a data URI, so WordPress colors it like the other menu icons. */ function rubik_chat_menu_icon() { $svg_path = plugin_dir_path( __FILE__ ) . 'assets/icon.svg'; if ( ! file_exists( $svg_path ) ) { return 'dashicons-format-chat'; } $svg = str_replace( 'currentColor', 'black', (string) file_get_contents( $svg_path ) ); return 'data:image/svg+xml;base64,' . base64_encode( $svg ); } add_action( 'admin_enqueue_scripts', 'rubik_chat_admin_styles' ); /* ===================================================== HELPERS ===================================================== */ function rubik_chat_log_api_call( $url, $method, $request_args, $response ) { $logs = get_option( 'rubikchat_api_logs', array() ); if ( ! is_array( $logs ) ) { $logs = array(); } $status_code = ''; $response_body = ''; if ( is_wp_error( $response ) ) { $status_code = 'ERROR'; $response_body = $response->get_error_message(); } else { $status_code = wp_remote_retrieve_response_code( $response ); $response_body = wp_remote_retrieve_body( $response ); } $entry = array( 'time' => current_time( 'mysql' ), 'url' => $url, 'method' => strtoupper( $method ), 'request' => isset( $request_args['body'] ) ? $request_args['body'] : '', 'status' => $status_code, 'response' => $response_body, ); array_unshift( $logs, $entry ); if ( count( $logs ) > 100 ) { $logs = array_slice( $logs, 0, 100 ); } update_option( 'rubikchat_api_logs', $logs ); } function rubik_chat_page_character_count( $post_id ) { $content = get_post_field( 'post_content', $post_id ); $content = wp_strip_all_tags( strip_shortcodes( $content ) ); $content = html_entity_decode( $content, ENT_QUOTES, 'UTF-8' ); $content = preg_replace( '/\s+/u', ' ', $content ); $content = trim( $content ); return function_exists( 'mb_strlen' ) ? mb_strlen( $content, 'UTF-8' ) : strlen( $content ); } function rubik_chat_get_selected_pages() { $saved = get_option( 'rubik_selected_pages', array() ); return is_array( $saved ) ? $saved : array(); } function rubik_chat_get_published_pages() { return get_posts( array( 'post_type' => 'page', 'post_status' => array( 'publish', 'private', 'draft', 'pending', 'future', 'protected' ), 'posts_per_page' => -1, 'orderby' => 'menu_order title', 'order' => 'ASC', ) ); } /** * Human-readable page visibility label for admin UI. */ /** * Approximate color name from hex for summary UI. */ function rubik_chat_color_name( $hex ) { $hex = strtolower( trim( (string) $hex ) ); if ( $hex === '' ) { return 'Default'; } if ( $hex[0] !== '#' ) { $hex = '#' . $hex; } if ( ! preg_match( '/^#[0-9a-f]{6}$/', $hex ) ) { return strtoupper( $hex ); } $r = hexdec( substr( $hex, 1, 2 ) ); $g = hexdec( substr( $hex, 3, 2 ) ); $b = hexdec( substr( $hex, 5, 2 ) ); $palette = array( 'Black' => array( 15, 23, 42 ), 'Charcoal' => array( 38, 38, 38 ), 'Gray' => array( 100, 116, 139 ), 'White' => array( 255, 255, 255 ), 'Red' => array( 239, 68, 68 ), 'Rose' => array( 244, 63, 94 ), 'Orange' => array( 249, 115, 22 ), 'Amber' => array( 245, 158, 11 ), 'Yellow' => array( 234, 179, 8 ), 'Green' => array( 34, 197, 94 ), 'Emerald' => array( 16, 185, 129 ), 'Teal' => array( 20, 184, 166 ), 'Cyan' => array( 6, 182, 212 ), 'Sky' => array( 14, 165, 233 ), 'Blue' => array( 59, 130, 246 ), 'Indigo' => array( 79, 70, 229 ), 'Violet' => array( 139, 92, 246 ), 'Purple' => array( 168, 85, 247 ), 'Fuchsia' => array( 217, 70, 239 ), 'Pink' => array( 236, 72, 153 ), ); $best = 'Custom'; $best_d = PHP_INT_MAX; foreach ( $palette as $name => $rgb ) { $d = ( $r - $rgb[0] ) ** 2 + ( $g - $rgb[1] ) ** 2 + ( $b - $rgb[2] ) ** 2; if ( $d < $best_d ) { $best_d = $d; $best = $name; } } return $best; } function rubik_chat_page_visibility_label( $post ) { if ( ! $post ) { return 'Unknown'; } // Password-protected pages still have status publish but post_password set if ( ! empty( $post->post_password ) ) { return 'Protected'; } $status = $post->post_status; $map = array( 'publish' => 'Public', 'private' => 'Private', 'draft' => 'Draft', 'pending' => 'Pending', 'future' => 'Scheduled', ); return $map[ $status ] ?? ucfirst( (string) $status ); } function rubik_chat_generate_password( $length = 12 ) { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $password = ''; for ( $i = 0; $i < $length; $i++ ) { $password .= $chars[ wp_rand( 0, strlen( $chars ) - 1 ) ]; } return $password; } /** * Extract rubikchat_session cookie value from an HTTP response. */ function rubik_chat_is_valid_bot_id_format( $bot_id ) { $bot_id = trim( (string) $bot_id ); if ( $bot_id === '' || strlen( $bot_id ) < 12 ) { return false; } if ( ! preg_match( '/^[A-Za-z0-9_-]+$/', $bot_id ) ) { return false; } return true; } function rubik_chat_is_generic_name( $name ) { $n = strtolower( trim( (string) $name ) ); return $n === '' || $n === 'connected agent' || $n === 'your agent' || $n === 'ai agent'; } /** * Parse agent name from welcome / initial messages when API display_name is empty. * e.g. "👋 Hi! I am RubikChat AI, ask me anything..." → "RubikChat AI" */ function rubik_chat_extract_name_from_messages( $messages ) { if ( empty( $messages ) ) { return ''; } if ( ! is_array( $messages ) ) { $messages = array( (string) $messages ); } $text = ''; foreach ( $messages as $m ) { if ( is_string( $m ) && trim( $m ) !== '' ) { $text = trim( $m ); break; } if ( is_array( $m ) && ! empty( $m['message'] ) && is_string( $m['message'] ) ) { $text = trim( $m['message'] ); break; } } if ( $text === '' ) { return ''; } $text = preg_replace( '/^[\p{So}\p{Sk}\s]+/u', '', $text ); $text = trim( $text ); if ( preg_match( '/(?:^|[.!?\s])(?:i\s+am|i\'m|my\s+name\s+is)\s+([A-Za-z0-9][A-Za-z0-9\s&\-]{0,40}?)(?:[,\.\!\?]|\s+ask|\s+how|\s+what|\s+about|$)/iu', $text, $m ) ) { $name = trim( $m[1] ); if ( $name !== '' && strlen( $name ) <= 40 ) { return sanitize_text_field( $name ); } } return ''; } /** * Fetch agent data for a specific botId from RubikChat. * Name order: display_name → botName → name → welcome message. */ function rubik_chat_fetch_agent_from_api( $bot_id ) { if ( ! rubik_chat_is_valid_bot_id_format( $bot_id ) ) { return array( 'ok' => false, 'message' => 'Please enter valid Agent ID', ); } $url = RUBIK_CHAT_STYLES_URL . rawurlencode( $bot_id ); $request_args = array( 'timeout' => 20, 'headers' => array( 'Accept' => 'application/json' ), ); $response = wp_remote_get( $url, $request_args ); rubik_chat_log_api_call( $url, 'GET', $request_args, $response ); if ( is_wp_error( $response ) ) { return array( 'ok' => false, 'message' => 'Could not reach RubikChat: ' . $response->get_error_message(), ); } $code = wp_remote_retrieve_response_code( $response ); $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); if ( $code === 404 || $code === 401 || $code === 403 ) { return array( 'ok' => false, 'message' => 'Please enter valid Agent ID', ); } if ( $code < 200 || $code >= 300 || ! is_array( $data ) ) { return array( 'ok' => false, 'message' => 'Please enter valid Agent ID', ); } if ( ! isset( $data['styles'] ) || ! is_array( $data['styles'] ) ) { return array( 'ok' => false, 'message' => 'Please enter valid Agent ID', ); } $styles = $data['styles']; $messages = isset( $data['initialMessages'] ) ? $data['initialMessages'] : array(); $name = ''; if ( ! empty( $styles['display_name'] ) ) { $name = sanitize_text_field( $styles['display_name'] ); } if ( $name === '' && ! empty( $data['botName'] ) ) { $name = sanitize_text_field( $data['botName'] ); } if ( $name === '' && ! empty( $data['name'] ) ) { $name = sanitize_text_field( $data['name'] ); } // When API display_name is empty, recover name from welcome text for this botId if ( $name === '' ) { $name = rubik_chat_extract_name_from_messages( $messages ); } return array( 'ok' => true, 'botName' => $name, 'display_name' => isset( $styles['display_name'] ) ? (string) $styles['display_name'] : '', 'styles' => $styles, 'initialMessages' => $messages, 'raw' => $data, ); } /** * Resolve botName for this botId only (never mix names across bots). */ function rubik_chat_resolve_agent_name( $bot_id, $api_name = '' ) { $api_name = is_string( $api_name ) ? trim( $api_name ) : ''; // 1) Name fetched for this botId from API / welcome if ( ! rubik_chat_is_generic_name( $api_name ) ) { return $api_name; } // 2) Saved name only if it belongs to the same botId $prev_id = get_option( 'rubik_bot_id', '' ); $prev_config = get_option( 'rubik_agent_config', array() ); if ( ! is_array( $prev_config ) ) { $prev_config = array(); } $prev_name = isset( $prev_config['botName'] ) ? trim( (string) $prev_config['botName'] ) : ''; if ( (string) $prev_id === (string) $bot_id && ! rubik_chat_is_generic_name( $prev_name ) ) { return $prev_name; } // 3) Build payload only if botId matches $build = get_option( 'rubik_agent_build', array() ); if ( is_array( $build ) ) { $build_bot = $build['botId'] ?? $build['bot_id'] ?? $build['chatbotId'] ?? $build['chatbot_id'] ?? $build['id'] ?? ( $build['data']['botId'] ?? $build['data']['id'] ?? '' ); $build_name = $build['botName'] ?? $build['bot_name'] ?? $build['name'] ?? ( $build['data']['botName'] ?? $build['data']['name'] ?? '' ); if ( (string) $build_bot === (string) $bot_id && is_string( $build_name ) && ! rubik_chat_is_generic_name( $build_name ) ) { return sanitize_text_field( trim( $build_name ) ); } } return 'Your Agent'; } /** * On settings load: if name is still generic, re-fetch for current botId and save botName. */ function rubik_chat_maybe_refresh_agent_name() { $bot_id = get_option( 'rubik_bot_id', '' ); if ( empty( $bot_id ) || get_option( 'rubik_connected', 'no' ) !== 'yes' ) { return; } $config = get_option( 'rubik_agent_config', array() ); if ( ! is_array( $config ) ) { $config = array(); } $current = isset( $config['botName'] ) ? trim( (string) $config['botName'] ) : ''; if ( ! rubik_chat_is_generic_name( $current ) ) { return; } $fetched = rubik_chat_fetch_agent_from_api( $bot_id ); if ( empty( $fetched['ok'] ) ) { return; } $name = rubik_chat_resolve_agent_name( $bot_id, $fetched['botName'] ?? '' ); if ( rubik_chat_is_generic_name( $name ) ) { return; } $config['botName'] = $name; update_option( 'rubik_agent_config', $config ); } /* ===================================================== SETTINGS PAGE ===================================================== */ function rubik_chat_settings() { if ( ! current_user_can( 'manage_options' ) ) { return; } // Refresh botName for current botId if still generic rubik_chat_maybe_refresh_agent_name(); $rubik_bot_id = get_option( 'rubik_bot_id', '' ); $is_connected = get_option( 'rubik_connected', 'no' ) === 'yes'; $saved_pages = rubik_chat_get_selected_pages(); $saved_config = get_option( 'rubik_agent_config', array() ); if ( ! is_array( $saved_config ) ) { $saved_config = array(); } $saved_bot_name = $saved_config['botName'] ?? ''; $saved_instructions = $saved_config['instructions'] ?? ''; $saved_llm = $saved_config['llm'] ?? 'gpt-4o-mini'; $saved_temperature = $saved_config['temperature'] ?? '0'; $saved_initial_message = $saved_config['initial_messages'] ?? ''; $saved_colour = $saved_config['color'] ?? '#4f46e5'; $saved_theme = $saved_config['theme'] ?? 'light'; $agent_is_built = ! empty( $rubik_bot_id ); $agent_type = get_option( 'rubik_agent_type', '' ); $agent_visible = get_option( 'rubik_visible_agent', 'yes' ) !== 'no'; $pages = rubik_chat_get_published_pages(); // Default agent name from domain + " Agent" $host_for_name = (string) wp_parse_url( home_url(), PHP_URL_HOST ); $base_for_name = strtolower( $host_for_name ); $base_for_name = preg_replace( '/\.local$/', '', $base_for_name ); $base_for_name = preg_replace( '/\.localhost$/', '', $base_for_name ); if ( $base_for_name === 'localhost' || $base_for_name === '127.0.0.1' || $base_for_name === '::1' || $base_for_name === '' ) { $base_for_name = get_bloginfo( 'name' ) ?: 'Site'; } $slug_for_name = preg_replace( '/[^a-z0-9]+/i', '', $base_for_name ); if ( $slug_for_name === '' ) { $slug_for_name = 'Site'; } $default_domain_agent_name = ucfirst( $slug_for_name ) . ' Agent'; if ( $saved_bot_name === '' || rubik_chat_is_generic_name( $saved_bot_name ) ) { $saved_bot_name = $default_domain_agent_name; } ?>

Welcome! Create a brand-new AI agent for your site
or reconnect to one you already have.

Note: By clicking "Create New Agent", an account will be automatically registered on RubikChat using your WordPress admin email address. Later in the setup process, the contents of the pages you select will be sent to RubikChat servers to train your AI chatbot.

Select Pages

Choose the published pages your AI Agent should use as knowledge.

No published pages found.

ID; $checked = false; foreach ( $saved_pages as $sp ) { if ( isset( $sp['page_id'] ) && (int) $sp['page_id'] === $page_id ) { $checked = true; break; } } $character_count = rubik_chat_page_character_count( $page_id ); ?>

Overview

Manage visibility and copy your Agent ID.

Agent ID
Show Agent on Website

Your AI Agent

Show Agent on Website

' . esc_html( rubik_chat_site_domain() ) . '' ); ?>

✓
Connected successfully You’re all set — let’s build your agent.

Choose the pages your agent should learn from and give it a personality.

'Permission denied.' ), 403 ); } $raw_pages = isset( $_POST['pages'] ) ? sanitize_text_field( wp_unslash( $_POST['pages'] ) ) : '[]'; $pages = json_decode( $raw_pages, true ); if ( ! is_array( $pages ) ) { wp_send_json_error( array( 'message' => 'Invalid page selection.' ), 400 ); } $selected = array(); foreach ( $pages as $page ) { $page_id = isset( $page['page_id'] ) ? absint( $page['page_id'] ) : 0; if ( ! $page_id ) continue; $post = get_post( $page_id ); if ( ! $post || $post->post_type !== 'page' ) continue; $selected[] = array( 'page_id' => $page_id, 'title' => get_the_title( $page_id ), 'url' => get_permalink( $page_id ), 'character_count' => rubik_chat_page_character_count( $page_id ), ); } update_option( 'rubik_selected_pages', $selected ); wp_send_json_success( array( 'message' => 'Pages saved.', 'count' => count( $selected ) ) ); } add_action( 'wp_ajax_rubik_chat_save_selected_pages', 'rubik_chat_save_selected_pages' ); function rubik_chat_create_agent_auto() { check_ajax_referer( 'rubik_chat_agent_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Permission denied.' ), 403 ); } $current_user = wp_get_current_user(); $email = $current_user->user_email; $domain = wp_parse_url( home_url(), PHP_URL_HOST ); $company_name = get_bloginfo( 'name' ); $password = rubik_chat_generate_password( 12 ); // Organization domain from site host name only: // tutorial18 / tutorial18.local → tutorial18com // nsu.local → nsucom // boot.local → bootcom $host = (string) $domain; if ( $host === '' ) { $host = (string) wp_parse_url( home_url(), PHP_URL_HOST ); } $is_local = ( $host === '' || strpos( $host, '.local' ) !== false || strpos( $host, 'localhost' ) !== false || $host === '127.0.0.1' || $host === '::1' ); if ( $is_local ) { // Drop TLD (.local etc.), keep the site name only, then append "com" $base = strtolower( $host ); $base = preg_replace( '/\.local$/', '', $base ); $base = preg_replace( '/\.localhost$/', '', $base ); if ( $base === 'localhost' || $base === '127.0.0.1' || $base === '::1' || $base === '' ) { $base = 'local'; } // letters/numbers only $slug = preg_replace( '/[^a-z0-9]/', '', $base ); if ( $slug === '' ) { $slug = 'local'; } $domain = $slug . 'com'; } if ( empty( $email ) || empty( $company_name ) ) { wp_send_json_error( array( 'message' => 'Could not get site information.' ), 400 ); } $request_args = array( 'timeout' => 30, 'headers' => array( 'Content-Type' => 'application/json', 'Accept' => 'application/json' ), 'body' => wp_json_encode( array( 'email' => $email, 'password' => $password, 'domain' => $domain, 'company_name' => $company_name, ) ), ); $register_response = wp_remote_post( RUBIK_CHAT_REGISTER_URL, $request_args ); rubik_chat_log_api_call( RUBIK_CHAT_REGISTER_URL, 'POST', $request_args, $register_response ); if ( is_wp_error( $register_response ) ) { wp_send_json_error( array( 'message' => 'Registration failed: ' . $register_response->get_error_message() ), 500 ); } $register_status = wp_remote_retrieve_response_code( $register_response ); $register_body = wp_remote_retrieve_body( $register_response ); $register_data = json_decode( $register_body, true ); $email_taken = is_array( $register_data ) && ! empty( $register_data['errors']['email'] ); if ( $email_taken || ( $register_status >= 400 && isset( $register_data['message'] ) && stripos( $register_data['message'], 'already' ) !== false ) ) { wp_send_json_error( array( 'message' => 'You already have an account. Please click "Connect Existing Agent".' ), 400 ); } if ( ! is_array( $register_data ) || $register_status < 200 || $register_status >= 300 ) { $msg = $register_data['message'] ?? 'Registration failed.'; if ( ! empty( $register_data['errors'] ) ) { $errors = array(); foreach ( $register_data['errors'] as $field => $messages ) { $errors[] = is_array( $messages ) ? implode( ', ', $messages ) : $messages; } $msg = implode( ' | ', $errors ); } wp_send_json_error( array( 'message' => $msg ), 400 ); } update_option( 'rubik_user_email', $email ); // Only the login token is kept (it doesn't expire); the password is not stored delete_option( 'rubik_api_secret' ); $organization = $register_data['organization'] ?? array(); $org_url = $organization['url'] ?? ''; $org_name = $organization['name'] ?? $company_name; $org_id = $organization['id'] ?? ''; $user = $register_data['user'] ?? array(); $user_id = $user['id'] ?? ''; if ( empty( $org_url ) ) { wp_send_json_error( array( 'message' => 'Organization URL not returned by API.' ), 500 ); } $login_args = array( 'timeout' => 30, 'headers' => array( 'Content-Type' => 'application/json', 'Accept' => 'application/json' ), 'body' => wp_json_encode( array( 'email' => $email, 'password' => $password ) ), ); $login_response = wp_remote_post( RUBIK_CHAT_LOGIN_URL, $login_args ); rubik_chat_log_api_call( RUBIK_CHAT_LOGIN_URL, 'POST', $login_args, $login_response ); if ( is_wp_error( $login_response ) ) { wp_send_json_error( array( 'message' => 'Account created but login failed.' ), 500 ); } $login_status = wp_remote_retrieve_response_code( $login_response ); $login_body = wp_remote_retrieve_body( $login_response ); $login_data = json_decode( $login_body, true ); if ( ! is_array( $login_data ) || $login_status < 200 || $login_status >= 300 ) { wp_send_json_error( array( 'message' => 'Account created but login failed.' ), 500 ); } $api_data = $login_data['data'] ?? $login_data; $token = $api_data['token'] ?? $api_data['access_token'] ?? $login_data['token'] ?? ''; if ( empty( $token ) ) { wp_send_json_error( array( 'message' => 'No token received after login.' ), 500 ); } // Prefer organization.url from login payload when present (source of truth for train route) $login_org = $api_data['organization'] ?? $login_data['organization'] ?? array(); if ( is_array( $login_org ) && ! empty( $login_org['url'] ) ) { $org_url = $login_org['url']; $org_name = $login_org['name'] ?? $org_name; $org_id = $login_org['id'] ?? $org_id; } if ( empty( $org_url ) && ! empty( $api_data['organization_url'] ) ) { $org_url = $api_data['organization_url']; } if ( empty( $org_url ) && ! empty( $api_data['org_url'] ) ) { $org_url = $api_data['org_url']; } if ( empty( $org_url ) ) { wp_send_json_error( array( 'message' => 'Organization URL missing from API response.' ), 500 ); } update_option( 'rubik_organization_name', sanitize_text_field( $org_name ) ); update_option( 'rubik_organization_url', sanitize_text_field( $org_url ) ); update_option( 'rubik_user_id', sanitize_text_field( (string) $user_id ) ); update_option( 'rubik_organization_id', sanitize_text_field( (string) $org_id ) ); update_option( 'rubik_token', sanitize_text_field( $token ) ); update_option( 'rubik_connected', 'yes' ); update_option( 'rubik_agent_type', 'agent_customize' ); update_option( 'rubik_visible_agent', 'yes' ); delete_option( 'rubik_bot_id' ); delete_option( 'rubik_selected_pages' ); delete_option( 'rubik_agent_config' ); delete_option( 'rubik_agent_build' ); delete_option( 'rubik_kb_id' ); delete_option( 'rubik_page_sources' ); delete_option( 'rubik_session_cookie' ); wp_send_json_success( array( 'message' => 'Agent created and connected successfully.', 'organization_url' => $org_url, ) ); } add_action( 'wp_ajax_rubik_chat_create_agent_auto', 'rubik_chat_create_agent_auto' ); /** * Connect existing agent by Bot ID. * Fetches name for THAT botId and saves as botName. */ /** * Connect existing agent by Bot ID. * - Always validates Bot ID via API. * - If Bot ID matches the one already stored in the database and a real agent name exists there, * reuse that agent_name from the database (do not overwrite config). * - Otherwise keep the original flow (fetch name, save defaults). */ function rubik_chat_connect_with_bot_id() { check_ajax_referer( 'rubik_chat_agent_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Permission denied.' ), 403 ); } $bot_id = isset( $_POST['bot_id'] ) ? sanitize_text_field( wp_unslash( $_POST['bot_id'] ) ) : ''; $bot_id = trim( $bot_id ); if ( $bot_id === '' ) { wp_send_json_error( array( 'message' => 'Please enter valid Agent ID' ), 400 ); } // Always validate Bot ID against RubikChat API. $fetched = rubik_chat_fetch_agent_from_api( $bot_id ); if ( empty( $fetched['ok'] ) ) { wp_send_json_error( array( 'message' => $fetched['message'] ?? 'Please enter valid Agent ID' ), 400 ); } $prev_bot = get_option( 'rubik_bot_id', '' ); $prev_config = get_option( 'rubik_agent_config', array() ); if ( ! is_array( $prev_config ) ) { $prev_config = array(); } $prev_name = isset( $prev_config['botName'] ) ? trim( (string) $prev_config['botName'] ) : ''; // Bot ID matches the one in the database → use agent_name from database. if ( (string) $prev_bot === (string) $bot_id && $prev_name !== '' && ! rubik_chat_is_generic_name( $prev_name ) ) { update_option( 'rubik_bot_id', $bot_id ); update_option( 'rubik_connected', 'yes' ); update_option( 'rubik_agent_type', 'agent_exist' ); // Keep existing rubik_agent_config untouched so the stored agent name remains. wp_send_json_success( array( 'message' => 'Connected successfully.', 'botId' => $bot_id, 'botName' => $prev_name, ) ); return; } // Different Bot ID (or no usable name in DB) → original flow. $bot_name = rubik_chat_resolve_agent_name( $bot_id, $fetched['botName'] ?? '' ); if ( (string) $prev_bot !== (string) $bot_id ) { delete_option( 'rubik_selected_pages' ); delete_option( 'rubik_kb_id' ); delete_option( 'rubik_page_sources' ); } update_option( 'rubik_bot_id', $bot_id ); update_option( 'rubik_agent_config', array( 'botName' => $bot_name, 'instructions' => '', 'llm' => 'gpt-4o-mini', 'temperature' => '0', 'initial_messages' => '', 'color' => '#4f46e5', 'theme' => 'light', ) ); update_option( 'rubik_connected', 'yes' ); update_option( 'rubik_agent_type', 'agent_exist' ); if ( get_option( 'rubik_visible_agent', '' ) === '' ) { update_option( 'rubik_visible_agent', 'yes' ); } wp_send_json_success( array( 'message' => 'Connected successfully.', 'botId' => $bot_id, 'botName' => $bot_name, ) ); } add_action( 'wp_ajax_rubik_chat_connect_with_bot_id', 'rubik_chat_connect_with_bot_id' ); function rubik_chat_set_visibility() { check_ajax_referer( 'rubik_chat_agent_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Permission denied.' ), 403 ); } $visible = isset( $_POST['visible'] ) ? sanitize_text_field( wp_unslash( $_POST['visible'] ) ) : 'yes'; $visible = ( $visible === 'yes' || $visible === '1' || $visible === 'true' ) ? 'yes' : 'no'; update_option( 'rubik_visible_agent', $visible ); wp_send_json_success( array( 'visible' => $visible === 'yes', 'message' => $visible === 'yes' ? 'Agent visible on website.' : 'Agent hidden on website.', ) ); } add_action( 'wp_ajax_rubik_chat_set_visibility', 'rubik_chat_set_visibility' ); function rubik_chat_logout() { check_ajax_referer( 'rubik_chat_agent_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Permission denied.' ), 403 ); } delete_option( 'rubik_connected' ); delete_option( 'rubik_bot_id' ); delete_option( 'rubik_agent_type' ); delete_option( 'rubik_selected_pages' ); delete_option( 'rubik_agent_config' ); delete_option( 'rubik_agent_build' ); delete_option( 'rubik_user_email' ); delete_option( 'rubik_api_secret' ); delete_option( 'rubik_visible_agent' ); delete_option( 'rubikchat_api_logs' ); delete_option( 'rubik_organization_name' ); delete_option( 'rubik_organization_url' ); delete_option( 'rubik_user_id' ); delete_option( 'rubik_organization_id' ); delete_option( 'rubik_token' ); delete_option( 'rubik_session_cookie' ); delete_option( 'rubik_kb_id' ); delete_option( 'rubik_page_sources' ); wp_send_json_success( array( 'message' => 'Disconnected successfully.' ) ); } add_action( 'wp_ajax_rubik_chat_logout', 'rubik_chat_logout' ); /** * Build agent instructions from selected page knowledge + site identity. * Matches RubikChat train-agent instructions format (role + grounding + knowledge). */ function rubik_chat_build_instructions_from_pages( $bot_name, $website_rows, $custom_instructions = '' ) { // Build instructions EXACTLY like sample API agent response (agent.instructions). // Selected page content goes in the final "5. About {Site}" block (knowledge). $site_name = get_bloginfo( 'name' ); if ( ! is_string( $site_name ) || trim( $site_name ) === '' ) { $site_name = ( is_string( $bot_name ) && $bot_name !== '' ) ? $bot_name : 'this website'; } $site_name = trim( $site_name ); // Knowledge from selected pages → same role as sample "About RubikChat" line $about_lines = array(); if ( is_array( $website_rows ) ) { foreach ( $website_rows as $row ) { $url = isset( $row['url'] ) ? trim( (string) $row['url'] ) : ''; $body = isset( $row['content'] ) ? trim( (string) $row['content'] ) : ''; $title = ''; if ( $url !== '' ) { $pid = function_exists( 'url_to_postid' ) ? url_to_postid( $url ) : 0; if ( $pid ) { $title = get_the_title( $pid ); } } if ( ! is_string( $title ) ) { $title = ''; } $title = trim( $title ); if ( $body === '' && $title === '' && $url === '' ) { continue; } // Cap each page so instructions stay within agent character limits if ( function_exists( 'mb_strlen' ) && mb_strlen( $body, 'UTF-8' ) > 6000 ) { $body = mb_substr( $body, 0, 6000, 'UTF-8' ) . '…'; } elseif ( strlen( $body ) > 6000 ) { $body = substr( $body, 0, 6000 ) . '…'; } // Format like sample: "Name | short description" then optional body $line = $title !== '' ? $title : $site_name; if ( $body !== '' ) { // Prefer first sentence / short blurb style when possible $line .= ' | ' . $body; } elseif ( $url !== '' ) { $line .= ' | ' . $url; } $about_lines[] = $line; } } if ( empty( $about_lines ) ) { $about_lines[] = $site_name; } $about_block = implode( "\r\n", $about_lines ); // Character-for-character structure of sample API agent instructions $nl = "\r\n"; $out = 'You are the professional AI assistant for ' . $site_name . '.' . $nl; $out .= '1. Role & Identity' . $nl; $out .= '- You are the official AI assistant representing ' . $site_name . '.' . $nl; $out .= '- You act as the primary digital contact for all user inquiries.' . $nl; $out .= '- Your primary goal: provide accurate information, answer questions, and guide users to take action.' . $nl; $out .= '2. Tone & Style' . $nl; $out .= '- Professional, clear, and respectful at all times.' . $nl; $out .= '- Use proper grammar and complete sentences.' . $nl; $out .= '- Avoid slang, humor, or overly casual language.' . $nl; $out .= '- Use bullet points or numbered steps when presenting multiple items.' . $nl; $out .= '3. Opening Behavior' . $nl; $out .= '- When a user sends their FIRST message or greeting (e.g., "hi", "hello"), you MUST:' . $nl; $out .= ' a) Greet them warmly.' . $nl; $out .= ' b) Introduce yourself: "I am your AI assistant for ' . $site_name . '."' . $nl; $out .= ' c) Briefly state what you can help with.' . $nl; $out .= ' d) Ask how you can assist them today.' . $nl; $out .= '4. Grounding & Accuracy' . $nl; $out .= '- You MUST ONLY answer factual questions based on the provided knowledge base and enabled tools.' . $nl; $out .= '- If a factual question is NOT in your knowledge base or tools, say: "I don\'t have that information right now, but I\'d be happy to connect you with our team."' . $nl; $out .= '- Never guess, fabricate, or assume factual information.' . $nl; $out .= '- Avoid off-topic discussions unrelated to ' . $site_name . ' and its services.' . $nl; $out .= '- IMPORTANT: Short conversational replies like "ok", "yes", "sure", "great", "sounds good", "proceed", "go ahead" are NOT questions. They are confirmations. Always treat them as the user agreeing to continue — never respond with "I can\'t help with that". Instead, continue guiding the user through the next step of the current workflow.' . $nl; $out .= '5. Conversation Best Practices' . $nl; $out .= '- Always respond in the same language the user is writing in.' . $nl; $out .= '- You are encouraged to use relevant emojis to make the conversation engaging and friendly.' . $nl; $out .= '- After completing any task, ask: "Is there anything else I can help you with?"' . $nl; $out .= '- If a user seems stuck or confused, proactively offer guidance.' . $nl; $out .= '- Keep responses concise — avoid walls of text unless the user asks for detail.' . $nl; $out .= '5. About ' . $site_name . $nl; $out .= $about_block; return $out; } function rubik_chat_build_agent() { try { check_ajax_referer( 'rubik_chat_agent_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Permission denied.' ), 403 ); } $org_slug = rubik_chat_org_slug(); if ( $org_slug === '' || get_option( 'rubik_token', '' ) === '' ) { wp_send_json_error( array( 'message' => 'Please connect an agent first.' ), 400 ); } // Prefer pages from this request, else DB $selected_pages = array(); if ( isset( $_POST['pages'] ) ) { $page_ids = json_decode( sanitize_text_field( wp_unslash( $_POST['pages'] ) ), true ); if ( is_array( $page_ids ) ) { foreach ( $page_ids as $pid ) { $page_id = absint( is_array( $pid ) ? ( $pid['page_id'] ?? 0 ) : $pid ); if ( ! $page_id ) { continue; } $post = get_post( $page_id ); if ( ! $post || $post->post_type !== 'page' ) { continue; } $selected_pages[] = array( 'page_id' => $page_id, 'title' => get_the_title( $page_id ), 'url' => get_permalink( $page_id ), 'character_count' => rubik_chat_page_character_count( $page_id ), ); } } } if ( empty( $selected_pages ) ) { $from_db = get_option( 'rubik_selected_pages', array() ); if ( is_array( $from_db ) ) { $selected_pages = $from_db; } } if ( empty( $selected_pages ) ) { wp_send_json_error( array( 'message' => 'Please select at least one page.' ), 400 ); } // Always persist so Selected Pages tab shows after build update_option( 'rubik_selected_pages', $selected_pages ); $botName = isset( $_POST['botName'] ) ? sanitize_text_field( wp_unslash( $_POST['botName'] ) ) : 'My AI Agent'; $custom_instr = isset( $_POST['instructions'] ) ? sanitize_textarea_field( wp_unslash( $_POST['instructions'] ) ) : ''; $llm = isset( $_POST['llm'] ) ? sanitize_text_field( wp_unslash( $_POST['llm'] ) ) : 'gpt-4o-mini'; $temperature = isset( $_POST['temperature'] ) ? floatval( $_POST['temperature'] ) : 0; $initial_messages = isset( $_POST['initial_messages'] ) ? sanitize_textarea_field( wp_unslash( $_POST['initial_messages'] ) ) : 'Hello! How can I help you today?'; $color = isset( $_POST['color'] ) ? sanitize_hex_color( wp_unslash( $_POST['color'] ) ) : '#4f46e5'; $theme = isset( $_POST['theme'] ) ? sanitize_key( wp_unslash( $_POST['theme'] ) ) : 'light'; if ( empty( $botName ) ) { $botName = 'My AI Agent'; } if ( empty( $initial_messages ) ) { $initial_messages = 'Hello! How can I help you today?'; } // The selected pages go to the knowledge base, so the instructions stay short $instructions = rubik_chat_default_instructions( $custom_instr ); update_option( 'rubik_agent_config', array( 'botName' => $botName, 'instructions' => $instructions, 'llm' => $llm, 'temperature' => $temperature, 'initial_messages' => $initial_messages, 'color' => $color, 'theme' => $theme, ) ); // 1. Knowledge base for the pages $kb = rubik_chat_ensure_knowledge_base( $botName ); if ( empty( $kb['ok'] ) ) { wp_send_json_error( array( 'message' => $kb['error'] ), 200 ); } // 2. The agent, with the knowledge base attached (skipped when it already exists) $bot_id = get_option( 'rubik_bot_id', '' ); if ( $bot_id === '' ) { $created = rubik_chat_api_request( 'POST', '/agents/onboarding/create/' . rawurlencode( $org_slug ), array( 'botName' => $botName, 'instructions' => $instructions, 'llm' => $llm, 'temperature' => (string) $temperature, // The API expects these lists as JSON strings 'initial_messages' => wp_json_encode( array( $initial_messages ) ), 'suggested_messages' => '[]', 'theme' => $theme, 'color' => $color ? $color : '#4f46e5', 'is_streaming' => '1', 'businessType' => wp_json_encode( array( 'label' => 'Other', 'value' => 'Other', 'type' => 'other' ) ), 'knowledge_base_ids' => wp_json_encode( array( (int) $kb['id'] ) ), // Lets the widget run on this site (Share & Embed > Allowed websites) 'website' => home_url(), ), 120 ); $bot_id = (string) ( $created['data']['agent']['chatbot_key'] ?? '' ); if ( ! $created['ok'] || $bot_id === '' ) { $msg = $created['error'] ?: 'Agent was not created (no Agent ID returned).'; if ( ! empty( $created['data']['details'] ) && is_string( $created['data']['details'] ) ) { $msg .= ' ' . $created['data']['details']; } wp_send_json_error( array( 'message' => $msg, 'status' => $created['status'] ), 200 ); } update_option( 'rubik_bot_id', sanitize_text_field( $bot_id ) ); } // Also for an agent created before RubikChat took the website on create rubik_chat_allow_this_site(); // 3. The selected pages as knowledge (RubikChat trains them in the background) $synced = rubik_chat_sync_page_sources( $kb['id'], wp_list_pluck( $selected_pages, 'page_id' ) ); $build_data = array( 'botId' => $bot_id, 'botName' => $botName, 'knowledge_base_id' => (int) $kb['id'], 'pages' => $synced, ); update_option( 'rubik_agent_build', $build_data ); $message = 'Agent built successfully! Your pages are being trained and will be used in answers shortly.'; if ( ! empty( $synced['errors'] ) ) { $message .= ' Some pages could not be added: ' . implode( '; ', $synced['errors'] ); } wp_send_json_success( array( 'message' => $message, 'botId' => $bot_id, 'botName' => $botName ) ); } catch ( Throwable $e ) { wp_send_json_error( array( 'message' => 'Fatal error: ' . $e->getMessage() ), 500 ); } } add_action( 'wp_ajax_rubik_chat_build_agent', 'rubik_chat_build_agent' ); function rubik_chat_retrain_agent() { check_ajax_referer( 'rubik_chat_agent_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Permission denied.' ), 403 ); } try { $bot_id = get_option( 'rubik_bot_id', '' ); if ( empty( $bot_id ) ) { wp_send_json_error( array( 'message' => 'Agent Bot ID is missing. Please create an agent first.' ), 400 ); } if ( get_option( 'rubik_token', '' ) === '' ) { wp_send_json_error( array( 'message' => 'Your RubikChat session is missing. Please disconnect and connect again.' ), 401 ); } $selected_page_ids = isset( $_POST['selected_pages'] ) ? array_map( 'intval', (array) wp_unslash( $_POST['selected_pages'] ) ) : array(); $selected_page_ids = array_values( array_filter( $selected_page_ids ) ); if ( empty( $selected_page_ids ) ) { wp_send_json_error( array( 'message' => 'No valid pages found to retrain.' ), 400 ); } $config = get_option( 'rubik_agent_config', array() ); $kb = rubik_chat_ensure_knowledge_base( is_array( $config ) ? ( $config['botName'] ?? '' ) : '' ); if ( empty( $kb['ok'] ) ) { wp_send_json_error( array( 'message' => $kb['error'] ), 200 ); } $synced = rubik_chat_sync_page_sources( $kb['id'], $selected_page_ids ); if ( ! empty( $synced['errors'] ) && $synced['added'] + $synced['updated'] === 0 ) { wp_send_json_error( array( 'message' => 'Retrain failed: ' . implode( '; ', $synced['errors'] ) ), 200 ); } $selected_to_save = array(); foreach ( $selected_page_ids as $pid ) { $selected_to_save[] = array( 'page_id' => $pid, 'title' => get_the_title( $pid ), 'url' => get_permalink( $pid ), 'character_count' => rubik_chat_page_character_count( $pid ), ); } update_option( 'rubik_selected_pages', $selected_to_save ); $message = 'Pages attached successfully and shortly updated data will be available to answer user questions.'; if ( ! empty( $synced['errors'] ) ) { $message .= ' Some pages could not be updated: ' . implode( '; ', $synced['errors'] ); } wp_send_json_success( array( 'message' => $message ) ); } catch ( Throwable $e ) { wp_send_json_error( array( 'message' => 'Fatal error: ' . $e->getMessage() ), 500 ); } } add_action( 'wp_ajax_rubik_chat_retrain_agent', 'rubik_chat_retrain_agent' ); /* ===================================================== FRONTEND EMBED ===================================================== */ /** * Playground test chat — proxy a message to RubikChat public/widget chat API. */ function rubik_chat_playground_message() { check_ajax_referer( 'rubik_chat_agent_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Unauthorized.' ), 403 ); } $message = isset( $_POST['message'] ) ? sanitize_text_field( wp_unslash( $_POST['message'] ) ) : ''; $bot_id = get_option( 'rubik_bot_id', '' ); if ( $message === '' || $bot_id === '' ) { wp_send_json_error( array( 'message' => 'Missing message or Agent ID.' ) ); } $user_id = get_current_user_id(); $conversation_key = 'rubik_playground_conversation_' . $user_id . '_' . md5( $bot_id ); $guest_user_id = get_user_meta( $user_id, 'rubik_playground_guest_id', true ); if ( ! $guest_user_id ) { $guest_user_id = wp_generate_uuid4(); update_user_meta( $user_id, 'rubik_playground_guest_id', $guest_user_id ); } // "New chat" from the admin screen starts a fresh conversation if ( ! empty( $_POST['reset'] ) ) { delete_transient( $conversation_key ); } $conversation_id = get_transient( $conversation_key ); $url = RUBIK_CHAT_API_BASE . '/chat'; $args = array( 'timeout' => 90, 'headers' => array( 'Content-Type' => 'application/json', 'Accept' => 'text/plain' ), 'body' => wp_json_encode( array( 'agent' => $bot_id, 'message' => $message, 'conversation_id' => $conversation_id ? $conversation_id : wp_generate_uuid4(), 'guest_user_id' => $guest_user_id, 'source' => 'playground', 'media' => array(), 'queryParams' => (object) array(), ) ), ); $response = wp_remote_post( $url, $args ); rubik_chat_log_api_call( $url, 'POST', $args, $response ); if ( is_wp_error( $response ) ) { wp_send_json_error( array( 'message' => 'Could not reach RubikChat: ' . $response->get_error_message() ) ); } $status = (int) wp_remote_retrieve_response_code( $response ); $raw = (string) wp_remote_retrieve_body( $response ); if ( $status < 200 || $status >= 300 ) { $data = json_decode( $raw, true ); $msg = is_array( $data ) ? ( $data['message'] ?? $data['error'] ?? '' ) : ''; wp_send_json_error( array( 'message' => $msg ? $msg : 'Chat API error (HTTP ' . $status . ').' ) ); } // The reply is a stream of "prefix: payload" lines (data:, completeMessage:, conversation:, ...) $streamed = ''; $complete = ''; $error = ''; foreach ( preg_split( "/\r?\n/", $raw ) as $line ) { if ( strpos( $line, 'completeMessage:' ) === 0 ) { $complete = trim( substr( $line, strlen( 'completeMessage:' ) ) ); } elseif ( strpos( $line, 'data_ui:' ) === 0 ) { continue; } elseif ( strpos( $line, 'data:' ) === 0 ) { $chunk = substr( $line, strlen( 'data:' ) ); $streamed .= ( strpos( $chunk, ' ' ) === 0 ) ? substr( $chunk, 1 ) : $chunk; } elseif ( strpos( $line, 'conversation:' ) === 0 ) { $conversation = json_decode( trim( substr( $line, strlen( 'conversation:' ) ) ), true ); if ( ! empty( $conversation[0]['id'] ) ) { set_transient( $conversation_key, sanitize_text_field( $conversation[0]['id'] ), DAY_IN_SECONDS ); } } elseif ( strpos( $line, 'credits:' ) === 0 ) { $credits = json_decode( trim( substr( $line, strlen( 'credits:' ) ) ), true ); $error = is_array( $credits ) ? ( $credits['message'] ?? 'Your RubikChat plan has no messages left.' ) : 'Your RubikChat plan has no messages left.'; } elseif ( strpos( $line, 'error:' ) === 0 ) { $decoded = json_decode( trim( substr( $line, strlen( 'error:' ) ) ), true ); $error = is_string( $decoded ) ? $decoded : trim( substr( $line, strlen( 'error:' ) ) ); } } $reply = $complete !== '' ? $complete : $streamed; // The API marks line breaks as "
" $reply = trim( preg_replace( '#\s*\s*#i', "\n", $reply ) ); if ( $reply === '' ) { wp_send_json_error( array( 'message' => $error !== '' ? $error : 'The agent did not return a reply. Please try again.' ) ); } wp_send_json_success( array( 'reply' => $reply ) ); } add_action( 'wp_ajax_rubik_chat_playground', 'rubik_chat_playground_message' ); function rubik_chat_embed_chatbot_script() { $bot_id = get_option( 'rubik_bot_id', '' ); if ( empty( $bot_id ) ) { return; } if ( get_option( 'rubik_connected', 'no' ) !== 'yes' ) { return; } // Show Agent on Website toggle if ( get_option( 'rubik_visible_agent', 'yes' ) === 'no' ) { return; } $frontend_css = plugin_dir_path( __FILE__ ) . 'assets/frontend.css'; if ( file_exists( $frontend_css ) ) { wp_enqueue_style( 'rubik-chat-frontend', plugins_url( 'assets/frontend.css', __FILE__ ), array(), filemtime( $frontend_css ) ); } $script_handle = 'rubik-chatbot-widget'; wp_enqueue_script( $script_handle, RUBIK_CHAT_WIDGET_SCRIPT, array(), '2.0.0', true ); wp_localize_script( $script_handle, 'embeddedChatbotConfig', array( 'chatbotId' => esc_attr( $bot_id ), 'domain' => 'widget.rubikchat.com', ) ); } add_action( 'wp_enqueue_scripts', 'rubik_chat_embed_chatbot_script' ); function rubik_chat_logs_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } if ( isset( $_POST['clear_logs'] ) && check_admin_referer( 'rubik_chat_clear_logs' ) ) { delete_option( 'rubikchat_api_logs' ); echo '

Logs cleared.

'; } $logs = get_option( 'rubikchat_api_logs', array() ); if ( ! is_array( $logs ) ) { $logs = array(); } ?>

API Logs

Latest API requests made by the RubikChat plugin (up to 100 entries).

No API logs found.

$log ) : $is_error = $log['status'] === 'ERROR' || (is_numeric($log['status']) && $log['status'] >= 400); $status_color = $is_error ? '#ef4444' : '#10b981'; $status_bg = $is_error ? '#fef2f2' : '#ecfdf5'; ?>
Time Method Status Endpoint & Details
array_values( $allowed ), 'visitor_daily_message_cap' => $current['data']['visitor_daily_message_cap'] ?? null, 'max_message_length' => $current['data']['max_message_length'] ?? null, ), 20 ); if ( ! $saved['ok'] ) { return false; } } update_option( 'rubik_site_allowed', $bot_id . '|' . $domain ); return true; } /** Once a day at most, when the plugin's page loads: fixes agents created by 2.0.0. */ function rubik_chat_heal_allowed_site() { if ( ! current_user_can( 'manage_options' ) ) { return; } if ( get_option( 'rubik_bot_id', '' ) === '' || get_transient( 'rubik_site_allowed_check' ) ) { return; } set_transient( 'rubik_site_allowed_check', 1, DAY_IN_SECONDS ); rubik_chat_allow_this_site(); } add_action( 'load-toplevel_page_rubik-chat', 'rubik_chat_heal_allowed_site' );