Source Code

DigitalCompass-AgentHub Community Edition

agenthub.php

<?php
/**
 * Plugin Name: DigitalCompass-AgentHub
 * Description: Connect any OpenClaw AI Agent to your WordPress website. Free & Open Source.
 * Version: 1.0.0
 * Author: Barbara Hohensee
 * Author URI: https://digitalcompass.site
 * Contributors: Barbara Hohensee
 * License: GPLv3
 * License URI: https://www.gnu.org/licenses/gpl-3.0.html
 */

if (!defined('ABSPATH')) exit;

function lisa_cat_i18n() {
    $lang = get_option('lisa_cat_chat_language', 'en');
    $strings = [
        'en' => [
            'admin_title'        => 'AgentHub Setup',
            'section_language'   => 'Language',
            'section_connection' => 'Connection',
            'section_chat'       => 'Chat Settings',
            'section_colors'     => 'Colors',
            'label_language'     => 'Interface Language',
            'label_endpoint'     => 'OpenClaw Endpoint',
            'label_token'        => 'Gateway Token',
            'label_model'        => 'Model',
            'label_ssl'          => 'SSL Verification',
            'label_ssl_desc'     => 'Verify SSL certificate (disable only for self-signed certificates)',
            'label_welcome'      => 'Welcome Message',
            'label_position'     => 'Position',
            'label_position_r'   => 'Bottom right',
            'label_position_l'   => 'Bottom left',
            'label_color_toggle' => 'Toggle Button',
            'label_color_header' => 'Header',
            'label_color_user'   => 'User Messages',
            'label_color_send'   => 'Send Button',
            'label_color_accent' => 'Accent Color (Focus, Links)',
            'save_button'        => 'Save Settings',
            'status_text'        => 'AI Assistant',
            'placeholder'        => 'Type a message...',
            'aria_input'         => 'Message to Agent',
            'aria_send'          => 'Send',
            'aria_open'          => 'Open chat',
            'error_general'      => 'An error occurred. Please try again.',
            'error_connection'   => 'Connection error. Please check your internet connection.',
        ],
        'de' => [
            'admin_title'        => 'AgentHub Einstellungen',
            'section_language'   => 'Sprache',
            'section_connection' => 'Verbindung',
            'section_chat'       => 'Chat Einstellungen',
            'section_colors'     => 'Farben',
            'label_language'     => 'Interface Sprache',
            'label_endpoint'     => 'OpenClaw Endpoint',
            'label_token'        => 'Gateway Token',
            'label_model'        => 'Model',
            'label_ssl'          => 'SSL Verifikation',
            'label_ssl_desc'     => 'SSL-Zertifikat verifizieren (deaktivieren nur bei selbstsignierten Zertifikaten)',
            'label_welcome'      => 'Begrüßungsnachricht',
            'label_position'     => 'Position',
            'label_position_r'   => 'Rechts unten',
            'label_position_l'   => 'Links unten',
            'label_color_toggle' => 'Toggle Button',
            'label_color_header' => 'Header',
            'label_color_user'   => 'Benutzer-Nachrichten',
            'label_color_send'   => 'Senden-Button',
            'label_color_accent' => 'Akzentfarbe (Focus, Links)',
            'save_button'        => 'Einstellungen speichern',
            'status_text'        => 'KI-Assistent',
            'placeholder'        => 'Nachricht schreiben...',
            'aria_input'         => 'Nachricht an Agent',
            'aria_send'          => 'Senden',
            'aria_open'          => 'Chat öffnen',
            'error_general'      => 'Ein Fehler ist aufgetreten. Bitte versuche es erneut.',
            'error_connection'   => 'Verbindungsfehler. Bitte prüfe deine Internetverbindung.',
        ],
    ];
    return $strings[$lang] ?? $strings['en'];
}

add_action('admin_enqueue_scripts', function ($hook) {
    if ($hook !== 'settings_page_lisa-cat-chat') return;
    wp_enqueue_style('wp-color-picker');
    wp_enqueue_script('wp-color-picker');
    wp_add_inline_script('wp-color-picker', 'jQuery(function($){ $(".lisa-color-picker").wpColorPicker(); });');
});

add_action('admin_menu', function () {
    add_options_page('AgentHub AI Infrastructure', 'AgentHub Setup ⚙️', 'manage_options', 'lisa-cat-chat', 'lisa_cat_chat_settings_page');
});

add_action('admin_init', function () {
    foreach ([
        'lisa_cat_chat_language', 'lisa_cat_chat_endpoint', 'lisa_cat_chat_token',
        'lisa_cat_chat_model', 'lisa_cat_chat_sslverify',
        'lisa_cat_chat_welcome', 'lisa_cat_chat_position',
        'lisa_cat_chat_color_toggle', 'lisa_cat_chat_color_header',
        'lisa_cat_chat_color_user_msg', 'lisa_cat_chat_color_send', 'lisa_cat_chat_color_accent',
    ] as $option) {
        register_setting('lisa_cat_chat', $option, [
            'sanitize_callback' => 'sanitize_text_field',
        ]);
    }
});

function lisa_cat_chat_settings_page() {
    $t    = lisa_cat_i18n();
    $lang = get_option('lisa_cat_chat_language', 'en');
    ?>
    <div class="wrap">
        <h1>🤖 <?php echo esc_html($t['admin_title']); ?></h1>
        <form method="post" action="options.php">
            <?php settings_fields('lisa_cat_chat'); ?>

            <h2><?php echo esc_html($t['section_language']); ?></h2>
            <table class="form-table">
                <tr>
                    <th><?php echo esc_html($t['label_language']); ?></th>
                    <td>
                        <select name="lisa_cat_chat_language">
                            <option value="en" <?php selected($lang, 'en'); ?>>English</option>
                            <option value="de" <?php selected($lang, 'de'); ?>>Deutsch</option>
                        </select>
                    </td>
                </tr>
            </table>

            <h2><?php echo esc_html($t['section_connection']); ?></h2>
            <table class="form-table">
                <tr>
                    <th><?php echo esc_html($t['label_endpoint']); ?></th>
                    <td><input type="text" name="lisa_cat_chat_endpoint" value="<?php echo esc_attr(get_option('lisa_cat_chat_endpoint', 'http://127.0.0.1:18789/v1/chat/completions')); ?>" class="regular-text" /></td>
                </tr>
                <tr>
                    <th><?php echo esc_html($t['label_token']); ?></th>
                    <td><input type="password" name="lisa_cat_chat_token" value="<?php echo esc_attr(get_option('lisa_cat_chat_token', '')); ?>" class="regular-text" /></td>
                </tr>
                <tr>
                    <th><?php echo esc_html($t['label_model']); ?></th>
                    <td><input type="text" name="lisa_cat_chat_model" value="<?php echo esc_attr(get_option('lisa_cat_chat_model', 'openclaw/cat')); ?>" class="regular-text" /></td>
                </tr>
                <tr>
                    <th><?php echo esc_html($t['label_ssl']); ?></th>
                    <td>
                        <label>
                            <input type="checkbox" name="lisa_cat_chat_sslverify" value="1" <?php checked(get_option('lisa_cat_chat_sslverify', '1'), '1'); ?> />
                            <?php echo esc_html($t['label_ssl_desc']); ?>
                        </label>
                    </td>
                </tr>
            </table>

            <h2><?php echo esc_html($t['section_chat']); ?></h2>
            <table class="form-table">
                <tr>
                    <th><?php echo esc_html($t['label_welcome']); ?></th>
                    <td><input type="text" name="lisa_cat_chat_welcome" value="<?php echo esc_attr(get_option('lisa_cat_chat_welcome', 'Hello! How can I help you?')); ?>" class="large-text" /></td>
                </tr>
                <tr>
                    <th><?php echo esc_html($t['label_position']); ?></th>
                    <td>
                        <select name="lisa_cat_chat_position">
                            <option value="right" <?php selected(get_option('lisa_cat_chat_position', 'right'), 'right'); ?>><?php echo esc_html($t['label_position_r']); ?></option>
                            <option value="left" <?php selected(get_option('lisa_cat_chat_position', 'right'), 'left'); ?>><?php echo esc_html($t['label_position_l']); ?></option>
                        </select>
                    </td>
                </tr>
            </table>

            <h2><?php echo esc_html($t['section_colors']); ?></h2>
            <table class="form-table">
                <tr>
                    <th><?php echo esc_html($t['label_color_toggle']); ?></th>
                    <td><input type="text" name="lisa_cat_chat_color_toggle" value="<?php echo esc_attr(get_option('lisa_cat_chat_color_toggle', '#c4623d')); ?>" class="lisa-color-picker" /></td>
                </tr>
                <tr>
                    <th><?php echo esc_html($t['label_color_header']); ?></th>
                    <td><input type="text" name="lisa_cat_chat_color_header" value="<?php echo esc_attr(get_option('lisa_cat_chat_color_header', '#1a5c6e')); ?>" class="lisa-color-picker" /></td>
                </tr>
                <tr>
                    <th><?php echo esc_html($t['label_color_user']); ?></th>
                    <td><input type="text" name="lisa_cat_chat_color_user_msg" value="<?php echo esc_attr(get_option('lisa_cat_chat_color_user_msg', '#1a5c6e')); ?>" class="lisa-color-picker" /></td>
                </tr>
                <tr>
                    <th><?php echo esc_html($t['label_color_send']); ?></th>
                    <td><input type="text" name="lisa_cat_chat_color_send" value="<?php echo esc_attr(get_option('lisa_cat_chat_color_send', '#c4623d')); ?>" class="lisa-color-picker" /></td>
                </tr>
                <tr>
                    <th><?php echo esc_html($t['label_color_accent']); ?></th>
                    <td><input type="text" name="lisa_cat_chat_color_accent" value="<?php echo esc_attr(get_option('lisa_cat_chat_color_accent', '#d97a5a')); ?>" class="lisa-color-picker" /></td>
                </tr>
            </table>

            <?php submit_button(esc_html($t['save_button'])); ?>
        </form>
        <hr>
        <p style="color:#999; font-size:12px;">
            AgentHub AI Infrastructure v1.0.0 — Free &amp; Open Source |
            <a href="https://digitalcompass.site/wp-agenthub" target="_blank">Upgrade to Pro →</a>
        </p>
    </div>
    <?php
}

add_action('wp_head', function () {
    ?>
    <style>
    :root {
        --cat-toggle:   <?php echo esc_attr(get_option('lisa_cat_chat_color_toggle', '#c4623d')); ?>;
        --cat-header:   <?php echo esc_attr(get_option('lisa_cat_chat_color_header', '#1a5c6e')); ?>;
        --cat-user-msg: <?php echo esc_attr(get_option('lisa_cat_chat_color_user_msg', '#1a5c6e')); ?>;
        --cat-send:     <?php echo esc_attr(get_option('lisa_cat_chat_color_send', '#c4623d')); ?>;
        --cat-accent:   <?php echo esc_attr(get_option('lisa_cat_chat_color_accent', '#d97a5a')); ?>;
        --cat-avatar-bg: #F0997B;
    }
    </style>
    <?php
});

add_action('rest_api_init', function () {
    register_rest_route('lisa/v1', '/chat', [
        'methods'             => 'POST',
        'callback'            => 'lisa_cat_chat_proxy',
        'permission_callback' => '__return_true',
    ]);
});

function lisa_cat_chat_proxy(WP_REST_Request $request) {
    $token     = get_option('lisa_cat_chat_token', '');
    $endpoint  = get_option('lisa_cat_chat_endpoint', 'http://127.0.0.1:18789/v1/chat/completions');
    $model     = get_option('lisa_cat_chat_model', 'openclaw/cat');
    $sslverify = (bool) get_option('lisa_cat_chat_sslverify', '1');

    if (empty($token)) {
        return new WP_Error('no_token', 'Gateway Token not configured', ['status' => 500]);
    }

    $messages = $request->get_param('messages');
    if (!is_array($messages) || empty($messages)) {
        return new WP_Error('no_messages', 'No messages provided', ['status' => 400]);
    }

    $ip       = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'] ?? 'unknown'));
    $rate_key = 'lisa_cat_rate_' . md5($ip);
    $count    = (int) get_transient($rate_key);
    if ($count >= 20) {
        return new WP_Error('rate_limit', 'Too many requests. Please wait a moment.', ['status' => 429]);
    }
    set_transient($rate_key, $count + 1, HOUR_IN_SECONDS);

    $response = wp_remote_post($endpoint, [
        'timeout'   => 60,
        'sslverify' => $sslverify,
        'headers'   => [
            'Content-Type'  => 'application/json',
            'Authorization' => 'Bearer ' . $token,
        ],
        'body' => wp_json_encode([
            'model'      => $model,
            'messages'   => $messages,
            'max_tokens' => 800,
        ]),
    ]);

    if (is_wp_error($response)) {
        return new WP_Error('openclaw_error', $response->get_error_message(), ['status' => 502]);
    }

    $code = wp_remote_retrieve_response_code($response);
    $data = json_decode(wp_remote_retrieve_body($response), true);

    if ($code !== 200) {
        return new WP_REST_Response(['error' => 'OpenClaw error', 'code' => $code], 502);
    }

    $content = $data['choices'][0]['message']['content'] ?? '';
    return new WP_REST_Response(['reply' => $content], 200);
}

add_action('wp_enqueue_scripts', function () {
    $t = lisa_cat_i18n();
    wp_enqueue_style('lisa-cat-chat', plugin_dir_url(__FILE__) . 'chat.css', [], '1.0.0');
    wp_enqueue_script('lisa-cat-chat', plugin_dir_url(__FILE__) . 'chat.js', [], '1.0.0', true);
    wp_localize_script('lisa-cat-chat', 'LisaCat', [
        'endpoint'         => rest_url('lisa/v1/chat'),
        'nonce'            => wp_create_nonce('wp_rest'),
        'welcome'          => get_option('lisa_cat_chat_welcome', 'Hello! How can I help you?'),
        'position'         => get_option('lisa_cat_chat_position', 'right'),
        'bot_name'         => 'AI Agent',
        'bot_avatar'       => '🤖',
        'status_text'      => $t['status_text'],
        'powered_text'     => 'DigitalCompass',
        'powered_url'      => 'https://digitalcompass.site',
        'placeholder'      => $t['placeholder'],
        'aria_input'       => $t['aria_input'],
        'aria_send'        => $t['aria_send'],
        'aria_open'        => $t['aria_open'],
        'error_general'    => $t['error_general'],
        'error_connection' => $t['error_connection'],
    ]);
});

chat.css

/* ── LISA CAT Chat Widget ── Deep Purple #26215C / Coral #F0997B ── */

@import url('https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Sans:wght@400;500;600&display=swap');

:root {
  --cat-purple:     #26215C;
  --cat-purple-mid: #3a3478;
  --cat-purple-light: #f0eeff;
  --cat-coral:      #F0997B;
  --cat-coral-dark: #d97a5a;
  --cat-white:      #ffffff;
  --cat-grey:       #f5f4fa;
  --cat-text:       #1a1730;
  --cat-muted:      #8b87a8;
  --cat-radius:     18px;
  --cat-shadow:     0 8px 40px rgba(38,33,92,0.18), 0 2px 8px rgba(38,33,92,0.10);
}

/* ── Toggle Button ── */
#lisa-cat-toggle {
  position: fixed;
  bottom: 28px;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: var(--cat-toggle, #c4623d);
  border: none;
  cursor: pointer;
  box-shadow: 0 4px 20px rgba(0,0,0,0.25);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 99998;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
  font-family: 'DM Sans', sans-serif;
}
#lisa-cat-toggle.position-right { right: 28px; }
#lisa-cat-toggle.position-left  { left: 28px; }

#lisa-cat-toggle:hover {
  transform: scale(1.08);
  box-shadow: 0 6px 28px rgba(38,33,92,0.45);
}

#lisa-cat-toggle svg {
  width: 28px;
  height: 28px;
  transition: opacity 0.2s;
}
#lisa-cat-toggle .icon-chat { opacity: 1; }
#lisa-cat-toggle .icon-close { opacity: 0; position: absolute; }
#lisa-cat-toggle.is-open .icon-chat  { opacity: 0; }
#lisa-cat-toggle.is-open .icon-close { opacity: 1; }

/* ── Chat Window ── */
#lisa-cat-window {
  position: fixed;
  bottom: 104px;
  width: 370px;
  height: 540px;
  background: var(--cat-white);
  border-radius: var(--cat-radius);
  box-shadow: var(--cat-shadow);
  display: flex;
  flex-direction: column;
  z-index: 99997;
  overflow: hidden;
  opacity: 0;
  transform: translateY(16px) scale(0.97);
  pointer-events: none;
  transition: opacity 0.25s ease, transform 0.25s ease;
  font-family: 'DM Sans', sans-serif;
}
#lisa-cat-window.position-right { right: 28px; }
#lisa-cat-window.position-left  { left: 28px; }

#lisa-cat-window.is-open {
  opacity: 1;
  transform: translateY(0) scale(1);
  pointer-events: all;
}

/* ── Header ── */
#lisa-cat-header {
  background: linear-gradient(135deg, var(--cat-header, #1a5c6e) 0%, var(--cat-header, #1a5c6e) 100%);
  padding: 18px 20px 16px;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
}

.cat-avatar {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: var(--cat-avatar-bg, #F0997B);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  flex-shrink: 0;
  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}

.cat-header-info { flex: 1; }

.cat-header-name {
  font-family: 'DM Serif Display', serif;
  font-size: 17px;
  color: var(--cat-white);
  line-height: 1.2;
  letter-spacing: 0.01em;
}

.cat-header-status {
  font-size: 12px;
  color: rgba(255,255,255,0.65);
  margin-top: 2px;
  display: flex;
  align-items: center;
  gap: 5px;
}

.cat-status-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #5ddf8a;
  box-shadow: 0 0 0 2px rgba(93,223,138,0.3);
  animation: pulse-dot 2s ease infinite;
}

@keyframes pulse-dot {
  0%, 100% { box-shadow: 0 0 0 2px rgba(93,223,138,0.3); }
  50%       { box-shadow: 0 0 0 5px rgba(93,223,138,0.1); }
}

/* ── Messages ── */
#lisa-cat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 20px 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  background: var(--cat-grey);
  scroll-behavior: smooth;
}

#lisa-cat-messages::-webkit-scrollbar { width: 4px; }
#lisa-cat-messages::-webkit-scrollbar-track { background: transparent; }
#lisa-cat-messages::-webkit-scrollbar-thumb { background: var(--cat-muted); border-radius: 4px; }

.cat-msg {
  max-width: 82%;
  padding: 10px 14px;
  border-radius: 14px;
  font-size: 14px;
  line-height: 1.55;
  animation: msg-in 0.22s ease;
}

@keyframes msg-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

.cat-msg.cat-msg--bot {
  background: var(--cat-white);
  color: var(--cat-text);
  border-bottom-left-radius: 4px;
  box-shadow: 0 1px 4px rgba(38,33,92,0.08);
  align-self: flex-start;
}

.cat-msg.cat-msg--user {
  background: var(--cat-user-msg, #1a5c6e);
  color: var(--cat-white);
  border-bottom-right-radius: 4px;
  align-self: flex-end;
}

/* ── Typing indicator ── */
.cat-typing {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 12px 14px;
  background: var(--cat-white);
  border-radius: 14px;
  border-bottom-left-radius: 4px;
  align-self: flex-start;
  box-shadow: 0 1px 4px rgba(38,33,92,0.08);
}

.cat-typing span {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--cat-muted);
  animation: typing-bounce 1.2s ease infinite;
}
.cat-typing span:nth-child(2) { animation-delay: 0.2s; }
.cat-typing span:nth-child(3) { animation-delay: 0.4s; }

@keyframes typing-bounce {
  0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
  30%           { transform: translateY(-6px); opacity: 1; }
}

/* ── Input Area ── */
#lisa-cat-input-area {
  padding: 14px 16px;
  background: var(--cat-white);
  border-top: 1px solid rgba(38,33,92,0.08);
  display: flex;
  gap: 10px;
  align-items: flex-end;
  flex-shrink: 0;
}

#lisa-cat-input {
  flex: 1;
  border: 1.5px solid rgba(38,33,92,0.15);
  border-radius: 12px;
  padding: 10px 14px;
  font-family: 'DM Sans', sans-serif;
  font-size: 14px;
  color: var(--cat-text);
  resize: none;
  outline: none;
  min-height: 42px;
  max-height: 100px;
  line-height: 1.45;
  background: var(--cat-grey);
  transition: border-color 0.2s;
}

#lisa-cat-input:focus {
  border-color: var(--cat-accent, #d97a5a);
  background: var(--cat-white);
}

#lisa-cat-input::placeholder { color: var(--cat-muted); }

#lisa-cat-send {
  width: 42px;
  height: 42px;
  border-radius: 12px;
  background: var(--cat-send, #c4623d);
  border: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: transform 0.15s, box-shadow 0.15s;
  box-shadow: 0 2px 8px rgba(240,153,123,0.4);
}

#lisa-cat-send:hover {
  transform: scale(1.06);
  box-shadow: 0 4px 14px rgba(240,153,123,0.5);
}

#lisa-cat-send:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}

#lisa-cat-send svg {
  width: 18px;
  height: 18px;
  color: white;
}

/* ── Powered by ── */
.cat-powered {
  text-align: center;
  font-size: 10px;
  color: #666;
  padding: 6px 0 10px;
  background: var(--cat-white);
  letter-spacing: 0.03em;
}

.cat-powered a {
  color: var(--cat-accent, #d97a5a);
  text-decoration: none;
  font-weight: 500;
}

/* ── Mobile ── */
@media (max-width: 480px) {
  #lisa-cat-window {
    width: calc(100vw - 20px);
    height: calc(100vh - 120px);
    right: 10px !important;
    left: 10px !important;
    bottom: 90px;
  }
}

chat.js

(function () {
  'use strict';

  const cfg      = window.LisaCat || {};
  const endpoint = cfg.endpoint        || '';
  const nonce    = cfg.nonce           || '';
  const welcome  = cfg.welcome         || 'Hello! How can I help you?';
  const position = cfg.position        || 'right';
  const botName    = cfg.bot_name      || 'CAT';
  const botAvatar  = cfg.bot_avatar    || '🦞';
  const statusText = cfg.status_text   || 'AI Assistant';
  const poweredText = cfg.powered_text || 'Powered by DIGITAL COMPASS';
  const poweredUrl  = cfg.powered_url  || 'https://digitalcompass.site';

  const i18n = {
    placeholder:      cfg.placeholder      || 'Type a message...',
    aria_input:       cfg.aria_input       || 'Message to Agent',
    aria_send:        cfg.aria_send        || 'Send',
    aria_open:        cfg.aria_open        || 'Open chat',
    error_general:    cfg.error_general    || 'An error occurred. Please try again.',
    error_connection: cfg.error_connection || 'Connection error. Please check your internet connection.',
  };

  const history = [];

  const toggleBtn = document.createElement('button');
  toggleBtn.id = 'lisa-cat-toggle';
  toggleBtn.className = 'position-' + position;
  toggleBtn.setAttribute('aria-label', i18n.aria_open);
  toggleBtn.innerHTML = `
    <svg class="icon-chat" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
      <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
    </svg>
    <svg class="icon-close" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
      <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
    </svg>
  `;

  const chatWindow = document.createElement('div');
  chatWindow.id = 'lisa-cat-window';
  chatWindow.className = 'position-' + position;
  chatWindow.setAttribute('role', 'dialog');
  chatWindow.setAttribute('aria-label', botName + ' Chatbot');
  chatWindow.innerHTML = `
    <div id="lisa-cat-header">
      <div class="cat-avatar">${botAvatar}</div>
      <div class="cat-header-info">
        <div class="cat-header-name">${botName}</div>
        <div class="cat-header-status">
          <span class="cat-status-dot"></span>
          ${statusText}
        </div>
      </div>
    </div>
    <div id="lisa-cat-messages" role="log" aria-live="polite"></div>
    <div id="lisa-cat-input-area">
      <textarea
        id="lisa-cat-input"
        placeholder="${i18n.placeholder}"
        rows="1"
        aria-label="${i18n.aria_input}"
      ></textarea>
      <button id="lisa-cat-send" aria-label="${i18n.aria_send}">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <line x1="22" y1="2" x2="11" y2="13"/>
          <polygon points="22 2 15 22 11 13 2 9 22 2"/>
        </svg>
      </button>
    </div>
    <div class="cat-powered">Powered by <a href="${poweredUrl}" target="_blank" rel="noopener">${poweredText}</a></div>
  `;

  document.body.appendChild(toggleBtn);
  document.body.appendChild(chatWindow);

  const messagesEl = document.getElementById('lisa-cat-messages');
  const inputEl    = document.getElementById('lisa-cat-input');
  const sendBtn    = document.getElementById('lisa-cat-send');

  let isOpen = false;
  toggleBtn.addEventListener('click', function () {
    isOpen = !isOpen;
    toggleBtn.classList.toggle('is-open', isOpen);
    chatWindow.classList.toggle('is-open', isOpen);
    if (isOpen && messagesEl.children.length === 0) {
      addMessage(welcome, 'bot');
    }
    if (isOpen) {
      setTimeout(() => inputEl.focus(), 300);
    }
  });

  function parseMarkdown(text) {
    return text
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
      .replace(/\*(.+?)\*/g, '<em>$1</em>')
      .replace(/`(.+?)`/g, '<code style="background:#f0f0f0;padding:1px 5px;border-radius:3px;font-size:12px;">$1</code>')
      .replace(/\[(.+?)\]\((https?:\/\/[^\)]+)\)/g, '<a href="$2" target="_blank" rel="noopener" style="color:var(--cat-accent,#d97a5a);">$1</a>')
      .replace(/^[\-\*] (.+)$/gm, '<li>$1</li>')
      .replace(/(<li>[\s\S]*<\/li>)/, '<ul style="margin:6px 0 6px 16px;padding:0;">$1</ul>')
      .replace(/\n<li>/g, '<li>')
      .replace(/<\/li>\n/g, '</li>')
      .replace(/\n/g, '<br>');
  }

  function addMessage(text, role) {
    const div = document.createElement('div');
    div.className = 'cat-msg cat-msg--' + role;
    if (role === 'bot') {
      div.innerHTML = parseMarkdown(text);
    } else {
      div.textContent = text;
    }
    messagesEl.appendChild(div);
    messagesEl.scrollTop = messagesEl.scrollHeight;
    return div;
  }

  function showTyping() {
    const div = document.createElement('div');
    div.className = 'cat-typing';
    div.id = 'lisa-cat-typing';
    div.innerHTML = '<span></span><span></span><span></span>';
    messagesEl.appendChild(div);
    messagesEl.scrollTop = messagesEl.scrollHeight;
  }

  function hideTyping() {
    const el = document.getElementById('lisa-cat-typing');
    if (el) el.remove();
  }

  async function sendMessage() {
    const text = inputEl.value.trim();
    if (!text) return;

    inputEl.value = '';
    inputEl.style.height = 'auto';
    sendBtn.disabled = true;

    addMessage(text, 'user');
    history.push({ role: 'user', content: text });

    showTyping();

    try {
      const res = await fetch(endpoint, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-WP-Nonce': nonce,
        },
        body: JSON.stringify({ messages: history }),
      });

      hideTyping();

      if (!res.ok) {
        const err = await res.json().catch(() => ({}));
        addMessage('⚠️ ' + (err.message || i18n.error_general), 'bot');
        return;
      }

      const data = await res.json();
      const reply = data.reply || '...';
      addMessage(reply, 'bot');
      history.push({ role: 'assistant', content: reply });

    } catch (e) {
      hideTyping();
      addMessage('⚠️ ' + i18n.error_connection, 'bot');
    } finally {
      sendBtn.disabled = false;
      inputEl.focus();
    }
  }

  inputEl.addEventListener('keydown', function (e) {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      sendMessage();
    }
  });

  sendBtn.addEventListener('click', sendMessage);

  inputEl.addEventListener('input', function () {
    this.style.height = 'auto';
    this.style.height = Math.min(this.scrollHeight, 100) + 'px';
  });

})();