diff --git a/css/workers-ai-chat.css b/css/workers-ai-chat.css new file mode 100644 index 0000000..e700c11 --- /dev/null +++ b/css/workers-ai-chat.css @@ -0,0 +1,137 @@ +.workers-ai-chat-container { + border: 1px solid #ccc; + padding: 15px; + max-width: 600px; + margin: 20px auto; + border-radius: 5px; + background-color: #f9f9f9; + font-family: Arial, sans-serif; +} + +/* Styles for predefined questions */ +.workers-ai-predefined-questions { + margin-bottom: 15px; + text-align: center; +} + +.workers-ai-predefined-questions .workers-ai-question-button { + margin: 5px; + padding: 8px 12px; + background-color: #0073aa; + color: #fff; + border: none; + border-radius: 3px; + cursor: pointer; + font-size: 14px; +} + +.workers-ai-predefined-questions .workers-ai-question-button:hover { + background-color: #005177; +} + +.workers-ai-chat-box { + max-height: 400px; + overflow-y: auto; + margin-bottom: 15px; + padding-right: 10px; +} + +.user-message, .ai-message { + margin-bottom: 10px; +} + +.user-message strong { + color: #0073aa; +} + +.ai-message strong { + color: #d54e21; +} + +/* Styles for Markdown content */ +.workers-ai-chat-box .ai-message, +.workers-ai-chat-box .user-message { + word-wrap: break-word; +} + +.workers-ai-chat-box h1, +.workers-ai-chat-box h2, +.workers-ai-chat-box h3, +.workers-ai-chat-box h4, +.workers-ai-chat-box h5, +.workers-ai-chat-box h6 { + margin: 10px 0 5px; + font-weight: bold; +} + +.workers-ai-chat-box p { + margin: 5px 0; +} + +.workers-ai-chat-box a { + color: #0073aa; + text-decoration: none; +} + +.workers-ai-chat-box a:hover { + text-decoration: underline; +} + +.workers-ai-chat-box ul, +.workers-ai-chat-box ol { + margin: 5px 0 5px 20px; +} + +.workers-ai-chat-box code { + background-color: #f5f5f5; + padding: 2px 4px; + border-radius: 3px; + font-family: monospace; +} + +.workers-ai-chat-box pre { + background-color: #f5f5f5; + padding: 10px; + border-radius: 3px; + overflow-x: auto; +} + +.workers-ai-chat-box img { + max-width: 100%; + height: auto; + border-radius: 3px; +} + +/* Styles for the input form */ +#workers-ai-chat-form { + display: flex; + flex-direction: column; +} + +#workers-ai-user-input { + padding: 8px; + border: 1px solid #ccc; + border-radius: 3px; + margin-bottom: 10px; + font-size: 14px; +} + +#workers-ai-submit-button { + padding: 8px 15px; + border: none; + background-color: #0073aa; + color: #fff; + border-radius: 3px; + cursor: pointer; + font-size: 14px; +} + +#workers-ai-submit-button:hover { + background-color: #005177; +} + +/* Styles for the "Thinking..." indicator */ +.loading-indicator { + font-style: italic; + color: #555; +} diff --git a/js/workers-ai-chat.js b/js/workers-ai-chat.js new file mode 100644 index 0000000..14eea07 --- /dev/null +++ b/js/workers-ai-chat.js @@ -0,0 +1,100 @@ +jQuery(document).ready(function($) { + // Function to convert Markdown to HTML using Marked.js + function convertMarkdownToHTML(markdownText) { + if (typeof marked !== 'undefined') { + // Configure Marked.js to sanitize generated HTML + marked.setOptions({ + sanitize: true, // Sanitize HTML to prevent XSS + breaks: true, // Optional: convert line breaks to <br> + }); + return marked.parse(markdownText); + } else { + // If Marked.js is not loaded, return the text unchanged + return $('<div>').text(markdownText).html(); + } + } + + // Handle form submission + $('#workers-ai-chat-form').on('submit', function(e) { + e.preventDefault(); + + var userInput = $('#workers-ai-user-input').val().trim(); + if (userInput === '') { + alert(workersAIChatData.error_empty_message || 'Please enter a message.'); + return; + } + + sendMessage(userInput); + }); + + // Handle clicks on predefined questions + $(document).on('click', '.workers-ai-question-button', function() { + var question = $(this).text().trim(); + if (question !== '') { + sendMessage(question); + } + }); + + // Function to send messages + function sendMessage(message) { + // If ephemeral chats are enabled, clear the chat history + if (workersAIChatData.ephemeral_chats === 'yes') { + $('#workers-ai-chat-box').empty(); + } + + // Display user's message + $('#workers-ai-chat-box').append('<div class="user-message"><strong>' + workersAIChatData.you_text + ':</strong> ' + $('<div>').text(message).html() + '</div>'); + + // Scroll to the bottom + $('#workers-ai-chat-box').scrollTop($('#workers-ai-chat-box')[0].scrollHeight); + + // Clear the input field + $('#workers-ai-user-input').val(''); + + // Show "Thinking..." indicator + var loadingHTML = '<div class="ai-message"><strong>' + workersAIChatData.ai_name + ':</strong> <span class="loading-indicator">' + workersAIChatData.thinking_text + '</span></div>'; + $('#workers-ai-chat-box').append(loadingHTML); + + // Scroll to the bottom + $('#workers-ai-chat-box').scrollTop($('#workers-ai-chat-box')[0].scrollHeight); + + // Disable the send button to prevent multiple submissions + $('#workers-ai-submit-button').prop('disabled', true); + + // Send AJAX request + $.ajax({ + type: 'POST', + url: workersAIChatData.ajax_url, + data: { + action: 'workers_ai_chat', + nonce: workersAIChatData.nonce, + user_input: message + }, + success: function(response) { + if (response.success) { + // Convert Markdown to HTML + var aiResponseHTML = convertMarkdownToHTML(response.data); + // Replace loading indicator with AI response + $('#workers-ai-chat-box .ai-message:last').html('<strong>' + workersAIChatData.ai_name + ':</strong> ' + aiResponseHTML); + } else { + $('#workers-ai-chat-box .ai-message:last').html('<strong>' + workersAIChatData.ai_name + ':</strong> <span style="color: red;">' + $('<div>').text(response.data).html() + '</span>'); + } + + // Scroll to the bottom + $('#workers-ai-chat-box').scrollTop($('#workers-ai-chat-box')[0].scrollHeight); + + // Enable the send button + $('#workers-ai-submit-button').prop('disabled', false); + }, + error: function() { + $('#workers-ai-chat-box .ai-message:last').html('<strong>' + workersAIChatData.ai_name + ':</strong> <span style="color: red;">' + workersAIChatData.error_processing_request + '</span>'); + + // Scroll to the bottom + $('#workers-ai-chat-box').scrollTop($('#workers-ai-chat-box')[0].scrollHeight); + + // Enable the send button + $('#workers-ai-submit-button').prop('disabled', false); + } + }); + } +}); diff --git a/languages/workers-ai-chat-es_ES.mo b/languages/workers-ai-chat-es_ES.mo new file mode 100644 index 0000000..2bc0b3a Binary files /dev/null and b/languages/workers-ai-chat-es_ES.mo differ diff --git a/languages/workers-ai-chat-es_ES.po b/languages/workers-ai-chat-es_ES.po new file mode 100644 index 0000000..7e5394c --- /dev/null +++ b/languages/workers-ai-chat-es_ES.po @@ -0,0 +1,134 @@ +# UserTemp <contacto@fernandodilland.com>, 2024. +msgid "" +msgstr "" +"Project-Id-Version: Workers AI Chat\n" +"POT-Creation-Date: 2023-10-11 12:00+0000\n" +"PO-Revision-Date: 2024-11-16 08:14-0600\n" +"Last-Translator: UserTemp <contacto@fernandodilland.com>\n" +"Language-Team: \n" +"Language: es_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Virtaal 0.7.1\n" + +#: workers-ai-chat.php:9 +msgid "Uses Cloudflare Workers AI in a web chat embedded via shortcode." +msgstr "Usa Cloudflare Workers AI en un chat web incrustrándolo vía shortcode." + +#: workers-ai-chat.php:23 +msgid "Workers AI Chat Settings" +msgstr "Ajustes de Workers AI Chat" + +#: workers-ai-chat.php:24 +msgid "Workers AI Chat" +msgstr "Workers AI Chat" + +#: workers-ai-chat.php:38 +msgid "Workers AI Chat Settings" +msgstr "Ajustes de Workrkers AI Chat" + +#: workers-ai-chat.php:67 +msgid "API and Security Settings" +msgstr "Ajustes de API y Seguridad" + +#: workers-ai-chat.php:94 +msgid "Account ID" +msgstr "Account ID" + +#: workers-ai-chat.php:101 +msgid "API Token" +msgstr "API Token" + +#: workers-ai-chat.php:108 +msgid "API Endpoint" +msgstr "Endpoint de API" + +#: workers-ai-chat.php:117 +msgid "Prompting Type" +msgstr "Tipo de prompt" + +#: workers-ai-chat.php:125 +msgid "System Message (for Scoped Prompts)" +msgstr "Mensaje de sistema (para prompts Scoped)" + +#: workers-ai-chat.php:135 +msgid "AI Name" +msgstr "Nombre de IA" + +#: workers-ai-chat.php:143 +msgid "Predefined Questions" +msgstr "Preguntas por defecto" + +#: workers-ai-chat.php:152 +msgid "Ephemeral Chats" +msgstr "Chats efímeros" + +#: workers-ai-chat.php:183 +msgid "" +"Enter the necessary settings to use the Cloudflare Workers AI API, as well as " +"the AI name and predefined questions:" +msgstr "" +"Escriba los ajustes necesarios para usar la API de Cloudflare Workers AI, de " +"igual forma el nombre de la IA y preguntas por defecto:" + +#: workers-ai-chat.php:223 +msgid "Unscoped" +msgstr "Unscoped" + +#: workers-ai-chat.php:224 +msgid "Scoped" +msgstr "Scoped" + +#: workers-ai-chat.php:229 +msgid "Only used if \"Scoped\" is selected in Prompting Type." +msgstr "Solo usa si \"Scoped\" está seleccionado en tipo de prompt" + +#: workers-ai-chat.php:236 +msgid "This will be the name displayed in the AI responses." +msgstr "Esto aparecerá como nombre en las respuestas de la IA." + +#: workers-ai-chat.php:243 +msgid "Enter predefined questions, one per line." +msgstr "Ingrese preguntas por defecto, una por línea." + +#: workers-ai-chat.php:250 +msgid "Enable ephemeral chats (default)" +msgstr "Habilitar chats efímeros (defecto)" + +#: workers-ai-chat.php:261 +msgid "Type a message..." +msgstr "Escriba un mensaje..." + +#: workers-ai-chat.php:261 +msgid "Send" +msgstr "Enviar" + +#: workers-ai-chat.php:283 +msgid "Incomplete plugin settings." +msgstr "Configuraciones de plugin incompletas" + +#: workers-ai-chat.php:307 +msgid "No response received." +msgstr "Respuesta no recibida" + +#: workers-ai-chat.php:309 +msgid "Unknown error." +msgstr "Error desconocido." + +#: js/workers-ai-chat.js:10 +msgid "Please enter a message." +msgstr "Por favor ingrese un mensaje." + +#: js/workers-ai-chat.js:30 +msgid "You" +msgstr "Tu" + +#: js/workers-ai-chat.js:60 +msgid "Thinking..." +msgstr "Pensando..." + +#: js/workers-ai-chat.js:86 +msgid "Error processing the request." +msgstr "Ha ocurrido un error procesando su solicitud." diff --git a/languages/workers-ai-chat-es_MX.mo b/languages/workers-ai-chat-es_MX.mo new file mode 100644 index 0000000..097f229 Binary files /dev/null and b/languages/workers-ai-chat-es_MX.mo differ diff --git a/languages/workers-ai-chat-es_MX.po b/languages/workers-ai-chat-es_MX.po new file mode 100644 index 0000000..814856f --- /dev/null +++ b/languages/workers-ai-chat-es_MX.po @@ -0,0 +1,134 @@ +# UserTemp <contacto@fernandodilland.com>, 2024. +msgid "" +msgstr "" +"Project-Id-Version: Workers AI Chat\n" +"POT-Creation-Date: 2023-10-11 12:00+0000\n" +"PO-Revision-Date: 2024-11-16 08:24-0600\n" +"Last-Translator: UserTemp <contacto@fernandodilland.com>\n" +"Language-Team: \n" +"Language: es_MX\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Virtaal 0.7.1\n" + +#: workers-ai-chat.php:9 +msgid "Uses Cloudflare Workers AI in a web chat embedded via shortcode." +msgstr "Usa Cloudflare Workers AI en un chat web incrustrándolo vía shortcode." + +#: workers-ai-chat.php:23 +msgid "Workers AI Chat Settings" +msgstr "Ajustes de Workers AI Chat" + +#: workers-ai-chat.php:24 +msgid "Workers AI Chat" +msgstr "Workers AI Chat" + +#: workers-ai-chat.php:38 +msgid "Workers AI Chat Settings" +msgstr "Ajustes de Workrkers AI Chat" + +#: workers-ai-chat.php:67 +msgid "API and Security Settings" +msgstr "Ajustes de API y Seguridad" + +#: workers-ai-chat.php:94 +msgid "Account ID" +msgstr "Account ID" + +#: workers-ai-chat.php:101 +msgid "API Token" +msgstr "API Token" + +#: workers-ai-chat.php:108 +msgid "API Endpoint" +msgstr "Endpoint de API" + +#: workers-ai-chat.php:117 +msgid "Prompting Type" +msgstr "Tipo de prompt" + +#: workers-ai-chat.php:125 +msgid "System Message (for Scoped Prompts)" +msgstr "Mensaje de sistema (para prompts Scoped)" + +#: workers-ai-chat.php:135 +msgid "AI Name" +msgstr "Nombre de IA" + +#: workers-ai-chat.php:143 +msgid "Predefined Questions" +msgstr "Preguntas por defecto" + +#: workers-ai-chat.php:152 +msgid "Ephemeral Chats" +msgstr "Chats efímeros" + +#: workers-ai-chat.php:183 +msgid "" +"Enter the necessary settings to use the Cloudflare Workers AI API, as well as " +"the AI name and predefined questions:" +msgstr "" +"Escriba los ajustes necesarios para usar la API de Cloudflare Workers AI, de " +"igual forma el nombre de la IA y preguntas por defecto:" + +#: workers-ai-chat.php:223 +msgid "Unscoped" +msgstr "Unscoped" + +#: workers-ai-chat.php:224 +msgid "Scoped" +msgstr "Scoped" + +#: workers-ai-chat.php:229 +msgid "Only used if \"Scoped\" is selected in Prompting Type." +msgstr "Solo usa si \"Scoped\" está seleccionado en tipo de prompt" + +#: workers-ai-chat.php:236 +msgid "This will be the name displayed in the AI responses." +msgstr "Esto aparecerá como nombre en las respuestas de la IA." + +#: workers-ai-chat.php:243 +msgid "Enter predefined questions, one per line." +msgstr "Ingrese preguntas por defecto, una por línea." + +#: workers-ai-chat.php:250 +msgid "Enable ephemeral chats (default)" +msgstr "Habilitar chats efímeros (defecto)" + +#: workers-ai-chat.php:261 +msgid "Type a message..." +msgstr "Escriba un mensaje..." + +#: workers-ai-chat.php:261 +msgid "Send" +msgstr "Enviar" + +#: workers-ai-chat.php:283 +msgid "Incomplete plugin settings." +msgstr "Configuraciones de plugin incompletas" + +#: workers-ai-chat.php:307 +msgid "No response received." +msgstr "Respuesta no recibida" + +#: workers-ai-chat.php:309 +msgid "Unknown error." +msgstr "Error desconocido." + +#: js/workers-ai-chat.js:10 +msgid "Please enter a message." +msgstr "Por favor ingrese un mensaje." + +#: js/workers-ai-chat.js:30 +msgid "You" +msgstr "Tu" + +#: js/workers-ai-chat.js:60 +msgid "Thinking..." +msgstr "Pensando..." + +#: js/workers-ai-chat.js:86 +msgid "Error processing the request." +msgstr "Ha ocurrido un error procesando su solicitud." diff --git a/languages/workers-ai-chat.pot b/languages/workers-ai-chat.pot new file mode 100644 index 0000000..ad4b52b --- /dev/null +++ b/languages/workers-ai-chat.pot @@ -0,0 +1,130 @@ +msgid "" +msgstr "" +"Project-Id-Version: Workers AI Chat\n" +"POT-Creation-Date: 2023-10-11 12:00+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: workers-ai-chat.php:9 +msgid "Uses Cloudflare Workers AI in a web chat embedded via shortcode." +msgstr "" + +#: workers-ai-chat.php:23 +msgid "Workers AI Chat Settings" +msgstr "" + +#: workers-ai-chat.php:24 +msgid "Workers AI Chat" +msgstr "" + +#: workers-ai-chat.php:38 +msgid "Workers AI Chat Settings" +msgstr "" + +#: workers-ai-chat.php:67 +msgid "API and Security Settings" +msgstr "" + +#: workers-ai-chat.php:94 +msgid "Account ID" +msgstr "" + +#: workers-ai-chat.php:101 +msgid "API Token" +msgstr "" + +#: workers-ai-chat.php:108 +msgid "API Endpoint" +msgstr "" + +#: workers-ai-chat.php:117 +msgid "Prompting Type" +msgstr "" + +#: workers-ai-chat.php:125 +msgid "System Message (for Scoped Prompts)" +msgstr "" + +#: workers-ai-chat.php:135 +msgid "AI Name" +msgstr "" + +#: workers-ai-chat.php:143 +msgid "Predefined Questions" +msgstr "" + +#: workers-ai-chat.php:152 +msgid "Ephemeral Chats" +msgstr "" + +#: workers-ai-chat.php:183 +msgid "" +"Enter the necessary settings to use the Cloudflare Workers AI API, as well as " +"the AI name and predefined questions:" +msgstr "" + +#: workers-ai-chat.php:223 +msgid "Unscoped" +msgstr "" + +#: workers-ai-chat.php:224 +msgid "Scoped" +msgstr "" + +#: workers-ai-chat.php:229 +msgid "Only used if \"Scoped\" is selected in Prompting Type." +msgstr "" + +#: workers-ai-chat.php:236 +msgid "This will be the name displayed in the AI responses." +msgstr "" + +#: workers-ai-chat.php:243 +msgid "Enter predefined questions, one per line." +msgstr "" + +#: workers-ai-chat.php:250 +msgid "Enable ephemeral chats (default)" +msgstr "" + +#: workers-ai-chat.php:261 +msgid "Type a message..." +msgstr "" + +#: workers-ai-chat.php:261 +msgid "Send" +msgstr "" + +#: workers-ai-chat.php:283 +msgid "Incomplete plugin settings." +msgstr "" + +#: workers-ai-chat.php:307 +msgid "No response received." +msgstr "" + +#: workers-ai-chat.php:309 +msgid "Unknown error." +msgstr "" + +#: js/workers-ai-chat.js:10 +msgid "Please enter a message." +msgstr "" + +#: js/workers-ai-chat.js:30 +msgid "You" +msgstr "" + +#: js/workers-ai-chat.js:60 +msgid "Thinking..." +msgstr "" + +#: js/workers-ai-chat.js:86 +msgid "Error processing the request." +msgstr "" diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..82c5d7f --- /dev/null +++ b/readme.txt @@ -0,0 +1,70 @@ +=== Workers AI Chat === +Contributors: fernandodilland +Tags: ai, chat, cloudflare, shortcode, chatbot +Requires at least: 5.0 +Tested up to: 6.7 +Stable tag: 1.3 +Requires PHP: 7.4 +License: GPLv2 or later +License URI: https://www.gnu.org/licenses/gpl-2.0.html + +Workers AI Chat allows you to integrate Cloudflare Workers AI into your WordPress site as a chat widget using a simple shortcode. + +== Description == + +Workers AI Chat is a powerful plugin that embeds an AI-powered chat widget on your WordPress site. The chat is powered by Cloudflare Workers AI and can be configured through an easy-to-use settings page. + +**Features:** +- Display the chat widget anywhere using the `[workers-ai]` shortcode. +- Configure API keys, endpoints, and chat behavior in the settings panel. +- Support for scoped and unscoped prompting types. +- Predefined questions for quick user interactions. +- Multilingual support with `.mo` and `.po` files for translations. +- Lightweight and customizable. + +**Use Cases:** +- Customer support automation. +- Website visitor engagement. +- Interactive FAQs. + +== Installation == + +1. Upload the `workers-ai-chat` folder to the `/wp-content/plugins/` directory. +2. Activate the plugin through the 'Plugins' menu in WordPress. +3. Navigate to 'Settings' > 'Workers AI Chat' to configure the plugin settings. +4. Add the `[workers-ai]` shortcode to any post, page, or widget to display the chat. + +== Frequently Asked Questions == + += How do I configure the API settings? = +Go to 'Settings' > 'Workers AI Chat' and fill in the required fields, such as Account ID, API Token, and API Endpoint. + += Can I customize the AI's name and predefined questions? = +Yes, you can customize the AI's name and predefined questions in the plugin settings under 'Settings' > 'Workers AI Chat.' + += Does the plugin support translations? = +Yes, the plugin is fully translatable. Place your language files in the `/languages/` directory or in `/wp-content/languages/plugins/`. + += Is the chat ephemeral? = +You can enable or disable ephemeral chats in the settings. When enabled, the chat history resets after each session. + +== Screenshots == + +1. **Chat Widget** - The AI chat interface displayed on the front end. +2. **Settings Page** - Configure API and behavior options. + +== Changelog == + += 2.2 = +* Added scoped prompting option with system messages. +* Improved multilingual support. +* Added ephemeral chat toggle. + +== Upgrade Notice == + += 2.2 = +Ensure your settings are updated to include the new options for scoped prompts and ephemeral chats. + +== License == + +This plugin is licensed under the GPLv2 or later. See [GPL-2.0 License](https://www.gnu.org/licenses/gpl-2.0.html) for more details. diff --git a/workers-ai-chat.php b/workers-ai-chat.php new file mode 100644 index 0000000..8809347 --- /dev/null +++ b/workers-ai-chat.php @@ -0,0 +1,461 @@ +<?php +/** + * Plugin Name: Workers AI Chat + * Description: Uses Cloudflare Workers AI in a web chat embedded via shortcode. + * Version: 1.3 + * Author: Fernando Dilland + * Author URI: https://fernandodilland.com + * Text Domain: workers-ai-chat + * Domain Path: /languages + * License: GPLv2 or later + * License URI: https://www.gnu.org/licenses/gpl-2.0.html + */ + +if (!defined('ABSPATH')) { + exit; // Prevent direct access +} + +class WorkersAIChat { + private $options; + + public function __construct() { + // Cargar el dominio de texto para traducciones con alta prioridad + add_action('plugins_loaded', array($this, 'load_textdomain'), 10); + + // Cargar configuraciones del plugin + add_action('admin_menu', array($this, 'add_plugin_page')); + add_action('admin_init', array($this, 'page_init')); + + // Registrar shortcode + add_shortcode('workers-ai', array($this, 'render_chat')); + + // Encolar scripts y estilos + add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts')); + + // Registrar manejadores AJAX + add_action('wp_ajax_workers_ai_chat', array($this, 'handle_ajax')); + add_action('wp_ajax_nopriv_workers_ai_chat', array($this, 'handle_ajax')); + } + + /** + * Cargar el dominio de texto para traducciones + */ + public function load_textdomain() { + $locale = apply_filters('plugin_locale', get_locale(), 'workers-ai-chat'); + $loaded = load_textdomain('workers-ai-chat', WP_LANG_DIR . '/workers-ai-chat/workers-ai-chat-' . $locale . '.mo'); + $loaded_plugin = load_plugin_textdomain('workers-ai-chat', false, dirname(plugin_basename(__FILE__)) . '/languages/'); + + // Eliminado: llamadas a error_log() + // Si necesitas manejar errores de carga del dominio de texto, considera otras alternativas. + } + + /** + * Agregar página de configuraciones al menú de administración + */ + public function add_plugin_page() { + add_options_page( + esc_html__('Workers AI Chat Settings', 'workers-ai-chat'), + esc_html__('Workers AI Chat', 'workers-ai-chat'), + 'manage_options', + 'workers-ai-chat', + array($this, 'create_admin_page') + ); + } + + /** + * Crear la página de configuración del plugin + */ + public function create_admin_page() { + $this->options = get_option('workers_ai_chat_options'); + ?> + <div class="wrap"> + <h1><?php esc_html_e('Workers AI Chat Settings', 'workers-ai-chat'); ?></h1> + <form method="post" action="options.php"> + <?php + settings_fields('workers_ai_chat_option_group'); + do_settings_sections('workers-ai-chat-admin'); + submit_button(); + ?> + </form> + </div> + <?php + } + + /** + * Inicializar configuraciones del plugin + */ + public function page_init() { + register_setting( + 'workers_ai_chat_option_group', // Grupo de opciones + 'workers_ai_chat_options', // Nombre de la opción + array($this, 'sanitize') // Callback de sanitización + ); + + add_settings_section( + 'setting_section_id', // ID + esc_html__('API and Security Settings', 'workers-ai-chat'), // Título + array($this, 'print_section_info'), // Callback + 'workers-ai-chat-admin' // Página + ); + + // Campos de configuración existentes + add_settings_field( + 'account_id', // ID + esc_html__('Account ID', 'workers-ai-chat'), // Título + array($this, 'account_id_callback'), // Callback + 'workers-ai-chat-admin', // Página + 'setting_section_id' // Sección + ); + + add_settings_field( + 'api_token', + esc_html__('API Token', 'workers-ai-chat'), + array($this, 'api_token_callback'), + 'workers-ai-chat-admin', + 'setting_section_id' + ); + + add_settings_field( + 'api_endpoint', + esc_html__('API Endpoint', 'workers-ai-chat'), + array($this, 'api_endpoint_callback'), + 'workers-ai-chat-admin', + 'setting_section_id' + ); + + add_settings_field( + 'prompting_type', + esc_html__('Prompting Type', 'workers-ai-chat'), + array($this, 'prompting_type_callback'), + 'workers-ai-chat-admin', + 'setting_section_id' + ); + + add_settings_field( + 'system_message', + esc_html__('System Message (for Scoped Prompts)', 'workers-ai-chat'), + array($this, 'system_message_callback'), + 'workers-ai-chat-admin', + 'setting_section_id' + ); + + // Campo para el Nombre de la AI + add_settings_field( + 'ai_name', + esc_html__('AI Name', 'workers-ai-chat'), + array($this, 'ai_name_callback'), + 'workers-ai-chat-admin', + 'setting_section_id' + ); + + // Campo para Preguntas Predefinidas + add_settings_field( + 'predefined_questions', + esc_html__('Predefined Questions', 'workers-ai-chat'), + array($this, 'predefined_questions_callback'), + 'workers-ai-chat-admin', + 'setting_section_id' + ); + + // Campo para Chats Efímeros + add_settings_field( + 'ephemeral_chats', + esc_html__('Ephemeral Chats', 'workers-ai-chat'), + array($this, 'ephemeral_chats_callback'), + 'workers-ai-chat-admin', + 'setting_section_id' + ); + } + + /** + * Sanitizar las entradas de configuración + */ + public function sanitize($input) { + $sanitized = array(); + if (isset($input['account_id'])) { + $sanitized['account_id'] = sanitize_text_field($input['account_id']); + } + if (isset($input['api_token'])) { + $sanitized['api_token'] = sanitize_text_field($input['api_token']); + } + if (isset($input['api_endpoint'])) { + $sanitized['api_endpoint'] = esc_url_raw($input['api_endpoint']); + } + if (isset($input['prompting_type'])) { + $sanitized['prompting_type'] = sanitize_text_field($input['prompting_type']); + } + if (isset($input['system_message'])) { + $sanitized['system_message'] = sanitize_textarea_field($input['system_message']); + } + if (isset($input['ai_name'])) { + $sanitized['ai_name'] = sanitize_text_field($input['ai_name']); + } + if (isset($input['predefined_questions'])) { + $sanitized['predefined_questions'] = sanitize_textarea_field($input['predefined_questions']); + } + if (isset($input['ephemeral_chats'])) { + $sanitized['ephemeral_chats'] = $input['ephemeral_chats'] === 'yes' ? 'yes' : 'no'; + } else { + $sanitized['ephemeral_chats'] = 'no'; + } + return $sanitized; + } + + /** + * Imprimir información de la sección de configuración + */ + public function print_section_info() { + esc_html_e('Enter the necessary settings to use the Cloudflare Workers AI API, as well as the AI name and predefined questions:', 'workers-ai-chat'); + } + + /** + * Callback para el campo Account ID + */ + public function account_id_callback() { + printf( + '<input type="text" id="account_id" name="workers_ai_chat_options[account_id]" value="%s" size="50"/>', + isset($this->options['account_id']) ? esc_attr($this->options['account_id']) : '' + ); + } + + /** + * Callback para el campo API Token + */ + public function api_token_callback() { + printf( + '<input type="text" id="api_token" name="workers_ai_chat_options[api_token]" value="%s" size="50"/>', + isset($this->options['api_token']) ? esc_attr($this->options['api_token']) : '' + ); + } + + /** + * Callback para el campo API Endpoint + */ + public function api_endpoint_callback() { + $default_endpoint = 'https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/ai/run/@cf/meta/llama-3.1-8b-instruct'; + printf( + '<input type="text" id="api_endpoint" name="workers_ai_chat_options[api_endpoint]" value="%s" size="50"/>', + isset($this->options['api_endpoint']) ? esc_attr($this->options['api_endpoint']) : esc_html($default_endpoint) + ); + } + + /** + * Callback para el campo Prompting Type + */ + public function prompting_type_callback() { + $options = isset($this->options['prompting_type']) ? $this->options['prompting_type'] : 'unscoped'; + ?> + <select id="prompting_type" name="workers_ai_chat_options[prompting_type]"> + <option value="unscoped" <?php selected($options, 'unscoped'); ?>><?php esc_html_e('Unscoped', 'workers-ai-chat'); ?></option> + <option value="scoped" <?php selected($options, 'scoped'); ?>><?php esc_html_e('Scoped', 'workers-ai-chat'); ?></option> + </select> + <?php + } + + /** + * Callback para el campo System Message + */ + public function system_message_callback() { + printf( + '<textarea id="system_message" name="workers_ai_chat_options[system_message]" rows="5" cols="50">%s</textarea>', + isset($this->options['system_message']) ? esc_textarea($this->options['system_message']) : '' + ); + echo '<p class="description">' . esc_html__('Only used if "Scoped" is selected in Prompting Type.', 'workers-ai-chat') . '</p>'; + } + + /** + * Callback para el campo AI Name + */ + public function ai_name_callback() { + printf( + '<input type="text" id="ai_name" name="workers_ai_chat_options[ai_name]" value="%s" size="50"/>', + isset($this->options['ai_name']) ? esc_attr($this->options['ai_name']) : 'AI' + ); + echo '<p class="description">' . esc_html__('This will be the name displayed in the AI responses.', 'workers-ai-chat') . '</p>'; + } + + /** + * Callback para el campo Predefined Questions + */ + public function predefined_questions_callback() { + printf( + '<textarea id="predefined_questions" name="workers_ai_chat_options[predefined_questions]" rows="5" cols="50">%s</textarea>', + isset($this->options['predefined_questions']) ? esc_textarea($this->options['predefined_questions']) : '' + ); + echo '<p class="description">' . esc_html__('Enter predefined questions, one per line.', 'workers-ai-chat') . '</p>'; + } + + /** + * Callback para el campo Ephemeral Chats + */ + public function ephemeral_chats_callback() { + $checked = isset($this->options['ephemeral_chats']) ? $this->options['ephemeral_chats'] : 'yes'; + ?> + <label> + <input type="checkbox" id="ephemeral_chats" name="workers_ai_chat_options[ephemeral_chats]" value="yes" <?php checked($checked, 'yes'); ?> /> + <?php esc_html_e('Enable ephemeral chats (default)', 'workers-ai-chat'); ?> + </label> + <?php + } + + /** + * Encolar scripts y estilos necesarios + */ + public function enqueue_scripts() { + if (is_singular() && has_shortcode(get_post()->post_content, 'workers-ai')) { + // Asegurar que las opciones estén cargadas + $this->options = get_option('workers_ai_chat_options'); + + // Encolar CSS con versión dinámica para evitar caché + $css_file = plugin_dir_path(__FILE__) . 'css/workers-ai-chat.css'; + $css_version = file_exists($css_file) ? filemtime($css_file) : '1.0'; + + wp_enqueue_style( + 'workers-ai-chat-css', + plugin_dir_url(__FILE__) . 'css/workers-ai-chat.css', + array(), + $css_version, // Parámetro de versión dinámico + 'all' + ); + + // Encolar JavaScript principal + wp_enqueue_script('workers-ai-chat-js', plugin_dir_url(__FILE__) . 'js/workers-ai-chat.js', array('jquery'), '2.1', true); + + // Encolar Marked.js localmente para evitar cargar scripts externos + wp_enqueue_script('marked-js', plugin_dir_url(__FILE__) . 'js/vendor/marked.min.js', array(), '4.3.0', true); + + // Obtener configuraciones + $ai_name = isset($this->options['ai_name']) ? esc_attr($this->options['ai_name']) : 'AI'; + $predefined_questions = isset($this->options['predefined_questions']) ? esc_textarea($this->options['predefined_questions']) : ''; + $ephemeral_chats = isset($this->options['ephemeral_chats']) ? $this->options['ephemeral_chats'] : 'yes'; + + // Localizar scripts con datos necesarios para JavaScript + wp_localize_script('workers-ai-chat-js', 'workersAIChatData', array( + 'ajax_url' => admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('workers_ai_chat_nonce'), + 'ai_name' => $ai_name, + 'predefined_questions' => $predefined_questions, + 'ephemeral_chats' => $ephemeral_chats, + 'thinking_text' => esc_html__('Thinking...', 'workers-ai-chat'), + 'you_text' => esc_html__('You', 'workers-ai-chat'), + 'error_processing_request' => esc_html__('Error processing the request.', 'workers-ai-chat'), + 'error_empty_message' => esc_html__('Please enter a message.', 'workers-ai-chat'), + )); + } + } + + /** + * Renderizar el shortcode del chat + */ + public function render_chat($atts) { + // Asegurar que las opciones estén cargadas + $this->options = get_option('workers_ai_chat_options'); + + // Obtener el nombre de la AI + $ai_name = isset($this->options['ai_name']) ? esc_attr($this->options['ai_name']) : 'AI'; + + // Obtener preguntas predefinidas + $predefined_questions = isset($this->options['predefined_questions']) ? $this->options['predefined_questions'] : ''; + $questions_array = array_filter(array_map('trim', explode("\n", $predefined_questions))); + + // Renderizar HTML del chat + ob_start(); + ?> + <div class="workers-ai-chat-container"> + <?php if (!empty($questions_array)): ?> + <div class="workers-ai-predefined-questions"> + <?php foreach ($questions_array as $question): ?> + <button type="button" class="workers-ai-question-button"><?php echo esc_html($question); ?></button> + <?php endforeach; ?> + </div> + <?php endif; ?> + <div class="workers-ai-chat-box" id="workers-ai-chat-box"> + <!-- Aquí aparecerán los mensajes del chat --> + </div> + <form id="workers-ai-chat-form"> + <input type="text" id="workers-ai-user-input" name="user_input" placeholder="<?php echo esc_attr__('Type a message...', 'workers-ai-chat'); ?>" required autofocus /> + <button type="submit" id="workers-ai-submit-button"><?php esc_html_e('Send', 'workers-ai-chat'); ?></button> + </form> + </div> + <?php + return ob_get_clean(); + } + + /** + * Manejar solicitudes AJAX + */ + public function handle_ajax() { + check_ajax_referer('workers_ai_chat_nonce', 'nonce'); + + // Obtener entrada del usuario + $user_input = isset($_POST['user_input']) ? sanitize_text_field( wp_unslash( $_POST['user_input'] ) ) : ''; + + // Obtener opciones del plugin + $options = get_option('workers_ai_chat_options'); + $account_id = isset($options['account_id']) ? sanitize_text_field($options['account_id']) : ''; + $api_token = isset($options['api_token']) ? sanitize_text_field($options['api_token']) : ''; + $api_endpoint = isset($options['api_endpoint']) ? esc_url_raw($options['api_endpoint']) : ''; + $prompting_type = isset($options['prompting_type']) ? sanitize_text_field($options['prompting_type']) : 'unscoped'; + $system_message = isset($options['system_message']) ? sanitize_textarea_field($options['system_message']) : ''; + $ai_name = isset($options['ai_name']) ? sanitize_text_field($options['ai_name']) : 'AI'; + + // Verificar configuraciones necesarias + if (empty($account_id) || empty($api_token) || empty($api_endpoint)) { + wp_send_json_error(esc_html__('Incomplete plugin settings.', 'workers-ai-chat')); + wp_die(); + } + + // Reemplazar {ACCOUNT_ID} en el endpoint + $api_url = str_replace('{ACCOUNT_ID}', urlencode($account_id), $api_endpoint); + + // Construir cuerpo de la solicitud basado en el tipo de prompting + if ($prompting_type === 'scoped') { + $messages = array( + array( + 'role' => 'system', + 'content' => $system_message + ), + array( + 'role' => 'user', + 'content' => $user_input + ) + ); + $body = wp_json_encode(array('messages' => $messages)); + } else { // Unscoped + $body = wp_json_encode(array('prompt' => $user_input)); + } + + // Realizar la solicitud a la API de Cloudflare Workers AI + $response = wp_remote_post($api_url, array( + 'headers' => array( + 'Authorization' => 'Bearer ' . $api_token, + 'Content-Type' => 'application/json', + ), + 'body' => $body, + 'timeout' => 60, + )); + + // Manejar errores de la solicitud + if (is_wp_error($response)) { + wp_send_json_error(esc_html__('Error connecting to the API: ', 'workers-ai-chat') . esc_html($response->get_error_message())); + wp_die(); + } + + // Obtener y decodificar la respuesta + $response_body = wp_remote_retrieve_body($response); + $decoded_response = json_decode($response_body, true); + + // Verificar si la respuesta es exitosa + if (isset($decoded_response['success']) && $decoded_response['success']) { + $ai_response = isset($decoded_response['result']['response']) ? sanitize_text_field($decoded_response['result']['response']) : esc_html__('No response received.', 'workers-ai-chat'); + wp_send_json_success($ai_response); + } else { + $error_messages = isset($decoded_response['errors']) ? implode(', ', array_map('esc_html', $decoded_response['errors'])) : esc_html__('Unknown error.', 'workers-ai-chat'); + wp_send_json_error(esc_html__('API Error: ', 'workers-ai-chat') . $error_messages); + } + + wp_die(); + } +} + +new WorkersAIChat();