- Created a new eveai_chat plugin to support the new dynamic possibilities of the Specialists. Currently only supports standard Rag retrievers (i.e. no extra arguments).
This commit is contained in:
108
integrations/Wordpress/eveai-chat/includes/class-assets.php
Normal file
108
integrations/Wordpress/eveai-chat/includes/class-assets.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
namespace EveAI\Chat;
|
||||
|
||||
class Assets implements Loadable {
|
||||
public function init() {
|
||||
add_action('wp_enqueue_scripts', [$this, 'register_assets']);
|
||||
add_action('wp_enqueue_scripts', [$this, 'maybe_load_assets']);
|
||||
}
|
||||
|
||||
public function register_assets() {
|
||||
// Register Socket.IO (from CDN)
|
||||
wp_register_script(
|
||||
'socket-io',
|
||||
'https://cdn.socket.io/4.0.1/socket.io.min.js',
|
||||
[],
|
||||
'4.0.1',
|
||||
true
|
||||
);
|
||||
|
||||
// Register Marked library for markdown (from CDN)
|
||||
wp_register_script(
|
||||
'marked',
|
||||
'https://cdn.jsdelivr.net/npm/marked/marked.min.js',
|
||||
[],
|
||||
'1.0.0',
|
||||
true
|
||||
);
|
||||
|
||||
// Register EveAI core scripts
|
||||
wp_register_script(
|
||||
'eveai-token-manager',
|
||||
EVEAI_CHAT_PLUGIN_URL . 'assets/js/eveai-token-manager.js',
|
||||
['socket-io'],
|
||||
EVEAI_CHAT_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_register_script(
|
||||
'eveai-sdk',
|
||||
EVEAI_CHAT_PLUGIN_URL . 'assets/js/eveai-sdk.js',
|
||||
['eveai-token-manager'],
|
||||
EVEAI_CHAT_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_register_script(
|
||||
'eveai-chat-widget',
|
||||
EVEAI_CHAT_PLUGIN_URL . 'assets/js/eveai-chat-widget.js',
|
||||
['eveai-sdk', 'marked'],
|
||||
EVEAI_CHAT_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
// Register styles
|
||||
wp_register_style(
|
||||
'eveai-chat',
|
||||
EVEAI_CHAT_PLUGIN_URL . 'assets/css/eveai-chat-style.css',
|
||||
[],
|
||||
EVEAI_CHAT_VERSION
|
||||
);
|
||||
|
||||
wp_register_style(
|
||||
'material-icons',
|
||||
'https://fonts.googleapis.com/icon?family=Material+Icons',
|
||||
[],
|
||||
EVEAI_CHAT_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
public function maybe_load_assets() {
|
||||
global $post;
|
||||
|
||||
// Only load if shortcode is present
|
||||
if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'eveai_chat')) {
|
||||
$this->load_assets();
|
||||
}
|
||||
}
|
||||
|
||||
private function load_assets() {
|
||||
// Enqueue all required scripts
|
||||
wp_enqueue_script('socket-io');
|
||||
wp_enqueue_script('marked');
|
||||
wp_enqueue_script('eveai-token-manager');
|
||||
wp_enqueue_script('eveai-sdk');
|
||||
wp_enqueue_script('eveai-chat-widget');
|
||||
|
||||
// Enqueue styles
|
||||
wp_enqueue_style('material-icons');
|
||||
wp_enqueue_style('eveai-chat');
|
||||
|
||||
// Localize script with WordPress-specific data
|
||||
wp_localize_script('eveai-sdk', 'eveaiWP', [
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
'settings' => $this->get_public_settings()
|
||||
]);
|
||||
}
|
||||
|
||||
private function get_public_settings() {
|
||||
$settings = get_option('eveai_chat_settings', []);
|
||||
|
||||
return [
|
||||
'socket_url' => $settings['socket_url'] ?? 'http://localhost:5002',
|
||||
'auth_url' => $settings['auth_url'] ?? 'http://localhost:5001',
|
||||
'tenant_id' => $settings['tenant_id'] ?? '',
|
||||
'wpBaseUrl' => rest_url(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace EveAI\Chat;
|
||||
|
||||
class CacheManager {
|
||||
private const SETTINGS_CACHE_KEY = 'eveai_settings_cache';
|
||||
private const CACHE_EXPIRATION = 3600; // 1 hour
|
||||
|
||||
public static function get_settings(): array {
|
||||
$cached = wp_cache_get(self::SETTINGS_CACHE_KEY);
|
||||
|
||||
if ($cached !== false) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$settings = get_option('eveai_chat_settings', []);
|
||||
wp_cache_set(self::SETTINGS_CACHE_KEY, $settings, '', self::CACHE_EXPIRATION);
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
public static function clear_settings_cache(): void {
|
||||
wp_cache_delete(self::SETTINGS_CACHE_KEY);
|
||||
}
|
||||
|
||||
public static function maybe_clear_caches(): void {
|
||||
if (defined('WP_CACHE') && WP_CACHE) {
|
||||
wp_cache_flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
125
integrations/Wordpress/eveai-chat/includes/class-plugin.php
Normal file
125
integrations/Wordpress/eveai-chat/includes/class-plugin.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
namespace EveAI\Chat;
|
||||
|
||||
class Plugin {
|
||||
/**
|
||||
* Plugin instance
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Plugin components
|
||||
*/
|
||||
private $components = [];
|
||||
|
||||
/**
|
||||
* Get plugin instance
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize plugin
|
||||
*/
|
||||
private function __construct() {
|
||||
$this->load_dependencies();
|
||||
$this->init_components();
|
||||
$this->register_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load dependencies
|
||||
*/
|
||||
private function load_dependencies() {
|
||||
// Core files
|
||||
require_once EVEAI_CHAT_PLUGIN_DIR . 'includes/interface-loadable.php';
|
||||
require_once EVEAI_CHAT_PLUGIN_DIR . 'includes/class-assets.php';
|
||||
require_once EVEAI_CHAT_PLUGIN_DIR . 'includes/class-shortcode.php';
|
||||
require_once EVEAI_CHAT_PLUGIN_DIR . 'includes/class-rest-controller.php';
|
||||
require_once EVEAI_CHAT_PLUGIN_DIR . 'includes/class-security.php';
|
||||
|
||||
// Admin
|
||||
if (is_admin()) {
|
||||
require_once EVEAI_CHAT_PLUGIN_DIR . 'admin/class-admin.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize components
|
||||
*/
|
||||
private function init_components() {
|
||||
// Initialize REST controller
|
||||
$this->components['rest'] = new RESTController();
|
||||
|
||||
// Initialize assets manager
|
||||
$this->components['assets'] = new Assets();
|
||||
|
||||
// Initialize shortcode handler
|
||||
$this->components['shortcode'] = new Shortcode();
|
||||
|
||||
// Initialize admin if in admin area
|
||||
if (is_admin()) {
|
||||
$this->components['admin'] = new Admin();
|
||||
}
|
||||
|
||||
// Initialize all components
|
||||
foreach ($this->components as $component) {
|
||||
if ($component instanceof Loadable) {
|
||||
$component->init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register WordPress hooks
|
||||
*/
|
||||
private function register_hooks() {
|
||||
// Plugin activation/deactivation
|
||||
register_activation_hook(EVEAI_CHAT_PLUGIN_DIR . 'eveai-chat.php', [$this, 'activate']);
|
||||
register_deactivation_hook(EVEAI_CHAT_PLUGIN_DIR . 'eveai-chat.php', [$this, 'deactivate']);
|
||||
|
||||
// Load text domain
|
||||
add_action('plugins_loaded', [$this, 'load_plugin_textdomain']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin activation
|
||||
*/
|
||||
public function activate() {
|
||||
// Set default options if not exists
|
||||
if (!get_option('eveai_chat_settings')) {
|
||||
add_option('eveai_chat_settings', [
|
||||
'auth_url' => 'https://api.askeveai.com',
|
||||
'socket_url' => 'https://chat.askeveai.com',
|
||||
'tenant_id' => '',
|
||||
'api_key' => ''
|
||||
]);
|
||||
}
|
||||
|
||||
// Clear permalinks
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin deactivation
|
||||
*/
|
||||
public function deactivate() {
|
||||
// Clear any scheduled hooks, clean up temporary data, etc.
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load plugin textdomain
|
||||
*/
|
||||
public function load_plugin_textdomain() {
|
||||
load_plugin_textdomain(
|
||||
'eveai-chat',
|
||||
false,
|
||||
dirname(plugin_basename(EVEAI_CHAT_PLUGIN_DIR)) . '/languages/'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
namespace EveAI\Chat;
|
||||
|
||||
class RESTController implements Loadable {
|
||||
const API_NAMESPACE = 'eveai/v1';
|
||||
|
||||
public function init() {
|
||||
add_action('rest_api_init', [$this, 'register_routes']);
|
||||
error_log('REST routes registered for EveAI Chat');
|
||||
}
|
||||
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
self::API_NAMESPACE,
|
||||
'/token',
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'get_token'],
|
||||
'permission_callback' => [$this, 'verify_request'],
|
||||
]
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
self::API_NAMESPACE,
|
||||
'/verify',
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'verify_token'],
|
||||
'permission_callback' => [$this, 'verify_request'],
|
||||
]
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
self::API_NAMESPACE,
|
||||
'/refresh',
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'refresh_token'],
|
||||
'permission_callback' => [$this, 'verify_request'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function verify_request(\WP_REST_Request $request): bool {
|
||||
// error_log('Verifying EveAI request: ' . print_r([
|
||||
// 'route' => $request->get_route(),
|
||||
// 'headers' => $request->get_headers(),
|
||||
// 'params' => $request->get_params()
|
||||
// ], true));
|
||||
|
||||
// Verify nonce
|
||||
$nonce = $request->get_header('X-WP-Nonce');
|
||||
if (!wp_verify_nonce($nonce, 'wp_rest')) {
|
||||
error_log('EveAI nonce verification failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify origin
|
||||
$origin = $request->get_header('origin');
|
||||
if (!$this->verify_origin($origin)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function get_token(\WP_REST_Request $request) {
|
||||
try {
|
||||
$settings = get_option('eveai_chat_settings');
|
||||
if (empty($settings['tenant_id']) || empty($settings['api_key'])) {
|
||||
return new \WP_Error(
|
||||
'configuration_error',
|
||||
'EveAI Chat is not properly configured.',
|
||||
['status' => 500]
|
||||
);
|
||||
}
|
||||
|
||||
$auth_url = rtrim($settings['auth_url'], '/');
|
||||
$token_endpoint = '/api/v1/auth/token';
|
||||
$full_url = $auth_url . $token_endpoint;
|
||||
|
||||
error_log('Attempting to get token from: ' . $full_url);
|
||||
|
||||
// Get decrypted API key
|
||||
$api_key = Security::decrypt_api_key($settings['api_key']);
|
||||
|
||||
$response = wp_remote_post($full_url, [
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json'
|
||||
],
|
||||
'body' => json_encode([
|
||||
'tenant_id' => $settings['tenant_id'],
|
||||
'api_key' => $api_key
|
||||
])
|
||||
]);
|
||||
|
||||
error_log('EveAI API Response: ' . print_r($response, true));
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
throw new \Exception($response->get_error_message());
|
||||
}
|
||||
|
||||
$body = json_decode(wp_remote_retrieve_body($response), true);
|
||||
|
||||
error_log('Token response body: ' . print_r($body, true)); // Add this for debugging
|
||||
|
||||
if (!isset($body['access_token'])) {
|
||||
throw new \Exception('No token in response');
|
||||
}
|
||||
|
||||
return new \WP_REST_Response($body, 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
error_log('EveAI token error: ' . $e->getMessage());
|
||||
return new \WP_Error(
|
||||
'token_error',
|
||||
$e->getMessage(),
|
||||
['status' => 500]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function verify_token(\WP_REST_Request $request) {
|
||||
try {
|
||||
$token = $request->get_header('Authorization');
|
||||
if (!$token) {
|
||||
throw new \Exception('No token provided');
|
||||
}
|
||||
|
||||
$settings = get_option('eveai_chat_settings');
|
||||
$response = wp_remote_post($settings['auth_url'] . '/auth/verify', [
|
||||
'headers' => ['Authorization' => $token]
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
throw new \Exception($response->get_error_message());
|
||||
}
|
||||
|
||||
$body = json_decode(wp_remote_retrieve_body($response), true);
|
||||
return new \WP_REST_Response($body, 200);
|
||||
} catch (\Exception $e) {
|
||||
return new \WP_Error(
|
||||
'verify_error',
|
||||
$e->getMessage(),
|
||||
['status' => 401]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function refresh_token(\WP_REST_Request $request) {
|
||||
try {
|
||||
$token = $request->get_header('Authorization');
|
||||
if (!$token) {
|
||||
throw new \Exception('No token provided');
|
||||
}
|
||||
|
||||
$settings = get_option('eveai_chat_settings');
|
||||
$response = wp_remote_post($settings['auth_url'] . '/auth/refresh', [
|
||||
'headers' => ['Authorization' => $token]
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
throw new \Exception($response->get_error_message());
|
||||
}
|
||||
|
||||
$body = json_decode(wp_remote_retrieve_body($response), true);
|
||||
return new \WP_REST_Response($body, 200);
|
||||
} catch (\Exception $e) {
|
||||
return new \WP_Error(
|
||||
'refresh_error',
|
||||
$e->getMessage(),
|
||||
['status' => 401]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function verify_origin($origin): bool {
|
||||
if (empty($origin)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$site_url = parse_url(get_site_url(), PHP_URL_HOST);
|
||||
$origin_host = parse_url($origin, PHP_URL_HOST);
|
||||
|
||||
return $origin_host === $site_url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
namespace EveAI\Chat;
|
||||
|
||||
class Security {
|
||||
private static $encryption_method = 'aes-256-cbc';
|
||||
|
||||
public static function encrypt_api_key(string $key): string {
|
||||
if (empty($key)) return '';
|
||||
|
||||
$salt = wp_salt('auth');
|
||||
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(self::$encryption_method));
|
||||
|
||||
$encrypted = openssl_encrypt(
|
||||
$key,
|
||||
self::$encryption_method,
|
||||
$salt,
|
||||
0,
|
||||
$iv
|
||||
);
|
||||
|
||||
return base64_encode($iv . $encrypted);
|
||||
}
|
||||
|
||||
public static function decrypt_api_key(string $encrypted): string {
|
||||
if (empty($encrypted)) return '';
|
||||
|
||||
$salt = wp_salt('auth');
|
||||
$data = base64_decode($encrypted);
|
||||
|
||||
$iv_length = openssl_cipher_iv_length(self::$encryption_method);
|
||||
$iv = substr($data, 0, $iv_length);
|
||||
$encrypted_data = substr($data, $iv_length);
|
||||
|
||||
return openssl_decrypt(
|
||||
$encrypted_data,
|
||||
self::$encryption_method,
|
||||
$salt,
|
||||
0,
|
||||
$iv
|
||||
);
|
||||
}
|
||||
|
||||
public static function generate_nonce(): string {
|
||||
return wp_create_nonce('eveai_chat_nonce');
|
||||
}
|
||||
|
||||
public static function verify_nonce(string $nonce): bool {
|
||||
return wp_verify_nonce($nonce, 'eveai_chat_nonce');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
namespace EveAI\Chat;
|
||||
|
||||
class Shortcode implements Loadable {
|
||||
public function init() {
|
||||
add_shortcode('eveai_chat', [$this, 'render_chat']);
|
||||
}
|
||||
|
||||
public function render_chat($atts) {
|
||||
$settings = get_option('eveai_chat_settings');
|
||||
error_log('Rendering chat with settings: ' . print_r($settings, true));
|
||||
|
||||
if (empty($settings['tenant_id']) || empty($settings['api_key'])) {
|
||||
return '<div class="eveai-error">' .
|
||||
esc_html__('EveAI Chat is not properly configured. Please check the admin settings.', 'eveai-chat') .
|
||||
'</div>';
|
||||
}
|
||||
|
||||
// Parse shortcode attributes
|
||||
$atts = shortcode_atts([
|
||||
'language' => 'en',
|
||||
'languages' => 'en',
|
||||
'specialist_id' => '1'
|
||||
], $atts, 'eveai_chat');
|
||||
|
||||
// Generate unique container ID
|
||||
$container_id = 'eveai-chat-' . uniqid();
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div id="<?php echo esc_attr($container_id); ?>" class="eveai-chat-container"></div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
console.log('Initializing EveAI Chat');
|
||||
const eveAI = new EveAI({
|
||||
tenantId: <?php echo esc_js($settings['tenant_id']); ?>,
|
||||
language: '<?php echo esc_js($atts['language']); ?>',
|
||||
languages: '<?php echo esc_js($atts['languages']); ?>',
|
||||
specialistId: '<?php echo esc_js($atts['specialist_id']); ?>',
|
||||
socketUrl: '<?php echo esc_js($settings['socket_url']); ?>',
|
||||
authUrl: '<?php echo esc_js($settings['auth_url']); ?>'
|
||||
});
|
||||
|
||||
try {
|
||||
await eveAI.initialize('<?php echo esc_js($container_id); ?>');
|
||||
console.log('Chat initialized successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize chat:', error);
|
||||
document.getElementById('<?php echo esc_js($container_id); ?>').innerHTML =
|
||||
'<div class="eveai-error">Failed to initialize chat. Please check console for details.</div>';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
namespace EveAI\Chat;
|
||||
|
||||
class RateLimiter {
|
||||
private const RATE_LIMIT_KEY = 'eveai_rate_limit_';
|
||||
private const MAX_REQUESTS = 60; // Maximum requests per window
|
||||
private const WINDOW_SECONDS = 60; // Time window in seconds
|
||||
|
||||
public static function check_rate_limit($identifier): bool {
|
||||
$key = self::RATE_LIMIT_KEY . $identifier;
|
||||
$requests = get_transient($key);
|
||||
|
||||
if ($requests === false) {
|
||||
set_transient($key, 1, self::WINDOW_SECONDS);
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($requests >= self::MAX_REQUESTS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
set_transient($key, $requests + 1, self::WINDOW_SECONDS);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function get_remaining_requests($identifier): int {
|
||||
$key = self::RATE_LIMIT_KEY . $identifier;
|
||||
$requests = get_transient($key);
|
||||
|
||||
if ($requests === false) {
|
||||
return self::MAX_REQUESTS;
|
||||
}
|
||||
|
||||
return max(0, self::MAX_REQUESTS - $requests);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace EveAI\Chat;
|
||||
|
||||
interface Loadable {
|
||||
/**
|
||||
* Initialize the component
|
||||
*/
|
||||
public function init();
|
||||
}
|
||||
Reference in New Issue
Block a user