Если вы когда-либо получали электронные письма типа: «Я не смог найти информацию на вашем сайте, не могли бы вы мне помочь?», то у вас уже есть хороший пример применения мини-чата с использованием ИИ. Цель здесь не в том, чтобы «заменить» вашу службу поддержки, а в том, чтобы предоставить полезный первоначальный ответ, основанный на содержании вашего сайта. без установить плагин.


Потребность / Вариант использования

«Простой» чат-бот с искусственным интеллектом на WordPress Его основная цель — ответить на повторяющиеся вопросы: часы работы, правила возврата, «где найти тот или иной товар». вОЗОБНОВЛЯЕТ Длинное руководство или справка по навигации («Мне нужен учебник по постоянным ссылкам»). В одном блоге я часто видел, что 80% входящих запросов сводятся к 10 вопросам.

Что предлагает нам ИИ: минималистичный интерфейс чата + безопасный AJAX-маршрут WordPress, который отправляет вопрос в API ИИ (например, OpenAI) и возвращает полезный ответ. Мы намеренно упростили всё: никаких сторонних виджетов, никаких SDK, никаких зависимостей от Composer.

В конце концов, вы узнаете:

  • Отображение изображения маленькой кошки (HTML/CSS/JS) с помощью шорткода WordPress.
  • Отправьте вопрос на свой сервер WordPress (AJAX) с помощью nonce (токена защиты от CSRF-атак).
  • Вызов API ИИ с помощью wp_remote_post()Обрабатывать ошибки/тайм-ауты и кэшировать ответы с помощью Transients.
  • Защитите свой API-ключ (хранящийся в wp-config.php) и избегайте предоставления к нему доступа браузеру.

Краткое резюме

  • Вы добавляете константу API в wp-config.php (никогда не по теме).
  • Вы создаете мю-плагин (плагин "must-use"), предоставляющий короткий код [ai_chatbot].
  • Фронт (JS) вызывает admin-ajax.php с использованием одноразового числа (nonce), а не напрямую API ИИ.
  • Сервер вызывает API через wp_remote_post()короткий тайм-аут, корректная обработка ошибок.
  • Кэш ответов (временных данных) для снижения затрат.
  • Очищенный ответ перед отображением (wp_kses()) для ограничения рисков XSS.

Когда следует использовать ИИ для этого?

Используйте этот тип чат-бота, если:

  • Вам задают повторяющиеся вопросы, и вы хотите получить немедленный «первый ответ».
  • Ваш контент уже достаточно обширен (часто задаваемые вопросы, страницы «О нас», руководства), и ИИ может помочь пользователю.
  • Вы признаете, что ответ иногда может быть не совсем корректным, и добавляете четкое сообщение («автоматический ответ»).
  • Вам нужен легкий, управляемый чат-бот, не полагающийся на непрозрачный плагин, который внедряет внешние скрипты повсюду.

Я часто рекомендую его для обучающих блогов, сайтов-витрин или ниш, где вопросы очень актуальны (например, «как забронировать», «сроки», «цены»).

Когда НЕ следует использовать ИИ

Избегайте использования ИИ, если классическое решение для WordPress справится с задачей лучше, проще и дешевле:

  • Внутренние исследования Начните с улучшения функциональности поиска (выдержки, категории, страница «Карта сайта»). Искусственный интеллект может обойтись дорого, если он просто посоветует вам «воспользоваться функцией поиска».
  • Поддержка по деликатным вопросам (Здравоохранение, юриспруденция, финансы): Риск галлюцинаций реален. В таких случаях форма обратной связи + база знаний будут безопаснее.
  • Вопросы сделок (команды, учетные записи): если ответ зависит от личных данных, не стоит наспех создавать "простой" чат с использованием ИИ. Вам необходима реальная аутентификация, права доступа и модель угроз.
  • Жесткий бюджет Если у вас небольшой трафик, всё в порядке. Но если у вас 50 000 посещений в день, затраты могут резко возрасти, если у вас нет ограничения на кэширование/количество запросов.

Предпосылки

Целевые версии : WordPress 6.9.4 (апрель 2026 г.) и PHP 8.1+.

Ключ API для ИИ Я использую OpenAI в качестве примера, потому что его API стабилен и хорошо документирован, но архитектура будет одинаковой и у Anthropic/Mistral/Google.

Краткое описание: вызовы API и HTTP.

Один API Это интерфейс, позволяющий запрашивать что-либо у сервиса (в данном случае: «ответьте на этот вопрос»). Технически, вы отправляете HTTP-запрос (часто в POST) в формате JSON, и вы получаете JSON в ответ.

В WordPress это корректно реализуется через HTTP API: wp_remote_post()wp_remote_get()Это рекомендуемый метод, поскольку он обрабатывает передачу данных (cURL, потоки), прокси-серверы и интегрируется с WordPress.

Официальный источник: HTTP API WordPress.

Где хранить ключ API (обязательно)

Вы будете хранить ключ в wp-config.phpНи в файле темы, ни тем более в JavaScript. Если ваш ключ окажется в браузере, он будет скопирован через 30 секунд и использован за ваш счёт.

Добавить это в wp-config.php (в идеале непосредственно перед фразой «Вот и всё, хватит редактировать!»):

define('BPCAB_OPENAI_API_KEY', 'VOTRE_CLE_ICI');

расходы Каждое сообщение, отправленное в API, стоит денег (за токен). Даже если это «несколько центов», сумма может накопиться. Поэтому мы реализуем кэширование и ограничение скорости запросов на стороне сервера.

Куда вставить код

  • Рекомендуемый вариант : un мю-плагин (заряжать автоматически). Путь : wp-content/mu-plugins/.
  • Альтернатива : классический пользовательский плагин в wp-content/plugins/.
  • Избегать : functions.php темы оформления (особенно если вы меняете тему или если конструктор обновляет файлы).

Официальная документация mu-плагинов: Обязательные плагины.

Архитектура решения

Вот ссылка на ленту новостей, изложенная простым языком:

  • Посетитель открывает страницу, содержащую [ai_chatbot].
  • Этот шорткод отображает пользовательский интерфейс (небольшое окно) и загружает JavaScript.
  • Когда пользователь отправляет сообщение, JavaScript отправляет POST-запрос. admin-ajax.php вместе с:
    • сообщение
    • nonce WordPress (запрос для защиты от подделки)
  • WordPress получает запрос через AJAX-хук, проверяет значение nonce, применяет ограничение скорости запросов, а затем вызывает API искусственного интеллекта. wp_remote_post().
  • WordPress проверяет полученный ответ, кэширует его (временно), а затем отправляет JSON обратно в браузер.
  • Браузер отображает ответ.

Почему следует использовать WordPress AJAX (а не вызывать API ИИ с помощью JavaScript)?

Потому что ваш ключ должен оставаться на стороне сервера. Если вы вызываете OpenAI напрямую из браузера, вы раскрываете ключ… и теряете контроль (из-за затрат, злоупотреблений, парсинга).

Полный код — шаг за шагом

Мы собираемся создать mu-плагин, который:

  • добавить шорткод [ai_chatbot]
  • CSS/JS подключаются без ошибок
  • Создает AJAX-запрос для авторизованных и неавторизованных пользователей.
  • вызывает API OpenAI
  • кэширует ответы

Шаг 1 — Создайте mu-плагин

Создайте папку wp-content/mu-plugins/ Если такого файла не существует, создайте его:

wp-content/mu-plugins/bpcab-ai-chatbot.php

<?php
/**
 * Plugin Name: BPCAB — Chatbot IA simple (sans plugin)
 * Description: Ajoute un chatbot IA minimal via shortcode, avec appel serveur (wp_remote_post), cache transient, et sécurité de base.
 * Author: Vous
 * Version: 1.0.0
 */

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

define('BPCAB_CHATBOT_VERSION', '1.0.0');

/**
 * Petit helper : récupère l'IP (approx) pour rate limiting.
 * Attention : derrière un proxy/CDN, l'IP peut être celle du proxy si mal configuré.
 */
function bpcab_get_client_ip(): string {
	$keys = array('HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR');
	foreach ($keys as $key) {
		if (!empty($_SERVER[$key])) {
			$ip = sanitize_text_field(wp_unslash($_SERVER[$key]));
			// X-Forwarded-For peut contenir plusieurs IP : on prend la première.
			if (str_contains($ip, ',')) {
				$parts = explode(',', $ip);
				$ip = trim($parts[0]);
			}
			return $ip;
		}
	}
	return '0.0.0.0';
}

Шаг 2 — Загрузка шорткода + CSS/JS

Un короткий код — это тег, заключенный в скобки (например, [ai_chatbot]), который WordPress заменяет HTML. Это удобно для Divi 5, Elementor и Avada: все они умеют вставлять шорткод (модуль «Код», виджет «Шорткод» и т. д.).

/**
 * Enqueue des assets uniquement si le shortcode est présent sur la page.
 */
function bpcab_maybe_enqueue_assets(): void {
	if (!is_singular()) {
		return;
	}

	global $post;
	if (!$post instanceof WP_Post) {
		return;
	}

	if (!has_shortcode($post->post_content, 'ai_chatbot')) {
		return;
	}

	$handle = 'bpcab-ai-chatbot';

	wp_register_style(
		$handle,
		plugins_url('bpcab-ai-chatbot.css', __FILE__),
		array(),
		BPCAB_CHATBOT_VERSION
	);

	wp_register_script(
		$handle,
		plugins_url('bpcab-ai-chatbot.js', __FILE__),
		array(),
		BPCAB_CHATBOT_VERSION,
		true
	);

	wp_enqueue_style($handle);
	wp_enqueue_script($handle);

	// Données côté JS (sans clé API !)
	wp_localize_script($handle, 'BPCAB_CHATBOT', array(
		'ajaxUrl' => admin_url('admin-ajax.php'),
		'nonce'   => wp_create_nonce('bpcab_chatbot_nonce'),
		'maxLen'  => 400,
	));
}
add_action('wp_enqueue_scripts', 'bpcab_maybe_enqueue_assets');

/**
 * Shortcode [ai_chatbot]
 */
function bpcab_ai_chatbot_shortcode(): string {
	// HTML volontairement simple, facile à styliser.
	ob_start();
	?>
	<div class="bpcab-chatbot" data-bpcab-chatbot="1">
		<div class="bpcab-chatbot__header">
			<strong>Assistant</strong>
			<span class="bpcab-chatbot__status" aria-live="polite">Prêt</span>
		</div>

		<div class="bpcab-chatbot__messages" role="log" aria-live="polite"></div>

		<form class="bpcab-chatbot__form">
			<input class="bpcab-chatbot__input" type="text" name="message" placeholder="Posez votre question…" autocomplete="off" />
			<button class="bpcab-chatbot__send" type="submit">Envoyer</button>
		</form>

		<p class="bpcab-chatbot__hint">
			<em>Réponse automatique. Ne partagez pas d’informations sensibles.</em>
		</p>
	</div>
	<?php
	return (string) ob_get_clean();
}
add_shortcode('ai_chatbot', 'bpcab_ai_chatbot_shortcode');

Распространенная ошибка — вставка этого кода в неправильный файл (фрагмент кода сборщика или плагин фрагмента кода, который запускается слишком поздно). С помощью mu-плагина вы избежите многих неожиданностей.

Шаг 3 — Создайте файлы CSS/JS

В том же файле mu-plugins, создавать:

  • bpcab-ai-chatbot.css
  • bpcab-ai-chatbot.js

Минималистичный CSS (Вы можете адаптировать его под тему/конструктор):

.bpcab-chatbot{
	max-width: 520px;
	border: 1px solid rgba(0,0,0,.12);
	border-radius: 12px;
	overflow: hidden;
	background: #fff;
}

.bpcab-chatbot__header{
	display:flex;
	justify-content: space-between;
	align-items:center;
	padding: 12px 14px;
	background: #111827;
	color: #fff;
}

.bpcab-chatbot__messages{
	padding: 12px 14px;
	min-height: 220px;
	max-height: 360px;
	overflow:auto;
	background: #f9fafb;
}

.bpcab-chatbot__msg{
	margin: 0 0 10px 0;
	padding: 10px 12px;
	border-radius: 10px;
	line-height: 1.35;
}

.bpcab-chatbot__msg--user{
	background: #dbeafe;
}

.bpcab-chatbot__msg--bot{
	background: #fff;
	border: 1px solid rgba(0,0,0,.08);
}

.bpcab-chatbot__form{
	display:flex;
	gap: 8px;
	padding: 12px 14px;
	border-top: 1px solid rgba(0,0,0,.08);
	background: #fff;
}

.bpcab-chatbot__input{
	flex:1;
	padding: 10px 12px;
	border: 1px solid rgba(0,0,0,.18);
	border-radius: 10px;
}

.bpcab-chatbot__send{
	padding: 10px 14px;
	border-radius: 10px;
	border: 0;
	background: #111827;
	color: #fff;
	cursor: pointer;
}

.bpcab-chatbot__hint{
	padding: 0 14px 14px 14px;
	margin: 0;
	color: #6b7280;
	font-size: 13px;
}

Минимальный JavaScript Отправка AJAX-запросов в WordPress, отображение сообщений, управление статусом.

(function () {
	function qs(root, sel) { return root.querySelector(sel); }

	function addMsg(messagesEl, text, who) {
		var p = document.createElement('p');
		p.className = 'bpcab-chatbot__msg bpcab-chatbot__msg--' + who;
		p.textContent = text;
		messagesEl.appendChild(p);
		messagesEl.scrollTop = messagesEl.scrollHeight;
	}

	function setStatus(root, text) {
		var s = qs(root, '.bpcab-chatbot__status');
		if (s) s.textContent = text;
	}

	function initChatbot(root) {
		var form = qs(root, '.bpcab-chatbot__form');
		var input = qs(root, '.bpcab-chatbot__input');
		var messages = qs(root, '.bpcab-chatbot__messages');

		if (!form || !input || !messages) return;

		addMsg(messages, "Bonjour ! Posez votre question, je vais essayer de vous orienter.", "bot");

		form.addEventListener('submit', function (e) {
			e.preventDefault();

			var msg = (input.value || '').trim();
			if (!msg) return;

			if (msg.length > (window.BPCAB_CHATBOT?.maxLen || 400)) {
				addMsg(messages, "Message trop long. Essayez de faire plus court.", "bot");
				return;
			}

			addMsg(messages, msg, "user");
			input.value = '';
			setStatus(root, 'Réflexion…');

			var data = new FormData();
			data.append('action', 'bpcab_chatbot_ask');
			data.append('nonce', window.BPCAB_CHATBOT?.nonce || '');
			data.append('message', msg);

			fetch(window.BPCAB_CHATBOT?.ajaxUrl || '', {
				method: 'POST',
				credentials: 'same-origin',
				body: data
			})
			.then(function (r) { return r.json(); })
			.then(function (payload) {
				if (!payload || !payload.success) {
					var err = payload?.data?.message || "Erreur côté serveur.";
					addMsg(messages, err, "bot");
					setStatus(root, 'Erreur');
					return;
				}
				addMsg(messages, payload.data.answer, "bot");
				setStatus(root, 'Prêt');
			})
			.catch(function () {
				addMsg(messages, "Impossible de contacter le serveur. Réessayez.", "bot");
				setStatus(root, 'Hors ligne');
			});
		});
	}

	document.addEventListener('DOMContentLoaded', function () {
		document.querySelectorAll('[data-bpcab-chatbot="1"]').forEach(initChatbot);
	});
})();

Распространенная ошибка: «JS не загружается». В 90% случаев это неправильный путь. plugins_url()или кэш (плагин/CDN), который обслуживает старую версию. Измените версию. BPCAB_CHATBOT_VERSION Чтобы принудительно перезагрузить страницу или очистить кэш.

Шаг 4 — AJAX-конечная точка + nonce + ограничение скорости запросов

Un крючок WordPress — это точка входа. Существует два типа:

  • действие : «сделайте здесь что-нибудь» (например, wp_enqueue_scripts).
  • фильтры : «изменить это значение» (например, the_content).

Здесь мы используем AJAX-запросы: wp_ajax_* (подключенные) и wp_ajax_nopriv_* (не подключено).

/**
 * Rate limit très simple : X requêtes par minute par IP.
 * Ce n'est pas une protection parfaite, mais ça évite les abus basiques.
 */
function bpcab_rate_limit_or_die(string $ip, int $limit = 10, int $window_seconds = 60): void {
	$key = 'bpcab_rl_' . md5($ip);
	$hits = (int) get_transient($key);

	if ($hits >= $limit) {
		wp_send_json_error(array(
			'message' => 'Trop de requêtes. Attendez une minute et réessayez.'
		), 429);
	}

	set_transient($key, $hits + 1, $window_seconds);
}

/**
 * Handler AJAX : reçoit la question et renvoie une réponse IA.
 */
function bpcab_ajax_chatbot_ask(): void {
	// Vérification nonce (anti-CSRF)
	$nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
	if (!wp_verify_nonce($nonce, 'bpcab_chatbot_nonce')) {
		wp_send_json_error(array('message' => 'Requête refusée (nonce invalide).'), 403);
	}

	$ip = bpcab_get_client_ip();
	bpcab_rate_limit_or_die($ip, 10, 60);

	$message = isset($_POST['message']) ? (string) wp_unslash($_POST['message']) : '';
	$message = trim($message);

	if ($message === '') {
		wp_send_json_error(array('message' => 'Message vide.'), 400);
	}

	if (mb_strlen($message) > 400) {
		wp_send_json_error(array('message' => 'Message trop long (max 400 caractères).'), 400);
	}

	$answer = bpcab_get_ai_answer($message);

	if (is_wp_error($answer)) {
		wp_send_json_error(array(
			'message' => $answer->get_error_message()
		), 500);
	}

	wp_send_json_success(array(
		'answer' => $answer,
	));
}
add_action('wp_ajax_bpcab_chatbot_ask', 'bpcab_ajax_chatbot_ask');
add_action('wp_ajax_nopriv_bpcab_chatbot_ask', 'bpcab_ajax_chatbot_ask');

Шаг 5 — Вызов OpenAI через wp_remote_post() + временный кэш

Мы собираемся:

  • Создайте ключ кэша на основе вопроса (хеша).
  • Установите значение TTL (например, 24 часа), чтобы избежать повторной оплаты за тот же ответ.
  • Добавьте короткий тайм-аут (например, 20 секунд), чтобы предотвратить «зависание» PHP.
  • Упростите ответ (допускается очень ограниченное количество HTML-кода).
/**
 * Appelle l'API IA (OpenAI) et renvoie une réponse texte.
 * IMPORTANT : la clé API doit être définie dans wp-config.php via BPCAB_OPENAI_API_KEY
 */
function bpcab_get_ai_answer(string $user_message) {
	if (!defined('BPCAB_OPENAI_API_KEY') || BPCAB_OPENAI_API_KEY === '') {
		return new WP_Error('bpcab_missing_key', 'Clé API manquante. Ajoutez BPCAB_OPENAI_API_KEY dans wp-config.php.');
	}

	// Cache : même question => même réponse pendant 24h
	$cache_key = 'bpcab_ai_' . md5(mb_strtolower(trim($user_message)));
	$cached = get_transient($cache_key);
	if (is_string($cached) && $cached !== '') {
		return $cached;
	}

	/**
	 * Prompt système : court, orienté "site".
	 * Ici, on ne fait PAS encore de RAG (recherche dans vos contenus).
	 * On limite la responsabilité : pas de conseils médicaux/juridiques.
	 */
	$system = "Vous êtes un assistant pour un site WordPress. Répondez en français, de façon courte et pratique. "
		. "Si la question demande des infos sensibles (santé/juridique/finance), refusez et conseillez de contacter le propriétaire du site. "
		. "Si vous ne savez pas, dites-le et proposez une piste (ex: consulter la page FAQ).";

	/**
	 * API OpenAI (Chat Completions style Responses API selon évolution).
	 * En avril 2026, l'API a évolué plusieurs fois. Le principe reste :
	 * - endpoint HTTPS
	 * - JSON en entrée
	 * - JSON en sortie
	 *
	 * Si votre compte OpenAI recommande un endpoint différent, adaptez l'URL et le parsing.
	 * Référez-vous à la doc officielle.
	 */
	$endpoint = 'https://api.openai.com/v1/chat/completions';

	$body = array(
		'model' => 'gpt-4.1-mini',
		'temperature' => 0.2,
		'messages' => array(
			array('role' => 'system', 'content' => $system),
			array('role' => 'user', 'content' => $user_message),
		),
	);

	$args = array(
		'headers' => array(
			'Authorization' => 'Bearer ' . BPCAB_OPENAI_API_KEY,
			'Content-Type'  => 'application/json',
		),
		'timeout' => 20,
		'body' => wp_json_encode($body),
	);

	$response = wp_remote_post($endpoint, $args);

	if (is_wp_error($response)) {
		return new WP_Error('bpcab_http_error', 'Erreur HTTP : ' . $response->get_error_message());
	}

	$code = (int) wp_remote_retrieve_response_code($response);
	$raw  = (string) wp_remote_retrieve_body($response);

	if ($code < 200 || $code >= 300) {
		// On évite d'afficher tout le raw (peut contenir des détails techniques).
		return new WP_Error('bpcab_api_error', 'API IA indisponible (code ' . $code . '). Vérifiez votre clé/quota.');
	}

	$data = json_decode($raw, true);
	if (!is_array($data)) {
		return new WP_Error('bpcab_json_error', 'Réponse API illisible (JSON invalide).');
	}

	// Parsing "chat.completions"
	$text = $data['choices'][0]['message']['content'] ?? '';
	$text = is_string($text) ? trim($text) : '';

	if ($text === '') {
		return new WP_Error('bpcab_empty_answer', 'Réponse vide de l’API IA.');
	}

	/**
	 * Assainissement :
	 * - on autorise un peu de HTML basique (liens, strong, em, br)
	 * - on supprime le reste
	 */
	$allowed = array(
		'a' => array(
			'href' => true,
			'target' => true,
			'rel' => true,
		),
		'strong' => array(),
		'em' => array(),
		'br' => array(),
	);

	$safe = wp_kses($text, $allowed);

	// Cache 24h
	set_transient($cache_key, $safe, DAY_IN_SECONDS);

	return $safe;
}

Две ошибки, которые я часто вижу:

  • Забыли поставить точку с запятой в wp-config.php или в mu-плагине: результат — белый экран (фатальная ошибка). Активировать WP_DEBUG На этапе подготовки, а не на этапе производства.
  • Скопировать старый учебник который использует старую структуру API или устаревшую модель. Здесь же используется база WordPress 6.9.4 + PHP 8.1, и вы адаптируете конечную точку только в том случае, если OpenAI изменит свои рекомендации.

Полный собранный код

Ниже приведён полный текст файла. мю-плагинВам также потребуется создать файлы CSS/JS (показаны выше). Не добавляйте ключ в этот файл.

<?php
/**
 * Plugin Name: BPCAB — Chatbot IA simple (sans plugin)
 * Description: Chatbot IA minimal via shortcode, AJAX WordPress, appel OpenAI avec wp_remote_post(), cache transient, sécurité de base.
 * Author: Vous
 * Version: 1.0.0
 */

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

define('BPCAB_CHATBOT_VERSION', '1.0.0');

function bpcab_get_client_ip(): string {
	$keys = array('HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR');
	foreach ($keys as $key) {
		if (!empty($_SERVER[$key])) {
			$ip = sanitize_text_field(wp_unslash($_SERVER[$key]));
			if (str_contains($ip, ',')) {
				$parts = explode(',', $ip);
				$ip = trim($parts[0]);
			}
			return $ip;
		}
	}
	return '0.0.0.0';
}

function bpcab_maybe_enqueue_assets(): void {
	if (!is_singular()) {
		return;
	}

	global $post;
	if (!$post instanceof WP_Post) {
		return;
	}

	if (!has_shortcode($post->post_content, 'ai_chatbot')) {
		return;
	}

	$handle = 'bpcab-ai-chatbot';

	wp_register_style(
		$handle,
		plugins_url('bpcab-ai-chatbot.css', __FILE__),
		array(),
		BPCAB_CHATBOT_VERSION
	);

	wp_register_script(
		$handle,
		plugins_url('bpcab-ai-chatbot.js', __FILE__),
		array(),
		BPCAB_CHATBOT_VERSION,
		true
	);

	wp_enqueue_style($handle);
	wp_enqueue_script($handle);

	wp_localize_script($handle, 'BPCAB_CHATBOT', array(
		'ajaxUrl' => admin_url('admin-ajax.php'),
		'nonce'   => wp_create_nonce('bpcab_chatbot_nonce'),
		'maxLen'  => 400,
	));
}
add_action('wp_enqueue_scripts', 'bpcab_maybe_enqueue_assets');

function bpcab_ai_chatbot_shortcode(): string {
	ob_start();
	?>
	<div class="bpcab-chatbot" data-bpcab-chatbot="1">
		<div class="bpcab-chatbot__header">
			<strong>Assistant</strong>
			<span class="bpcab-chatbot__status" aria-live="polite">Prêt</span>
		</div>

		<div class="bpcab-chatbot__messages" role="log" aria-live="polite"></div>

		<form class="bpcab-chatbot__form">
			<input class="bpcab-chatbot__input" type="text" name="message" placeholder="Posez votre question…" autocomplete="off" />
			<button class="bpcab-chatbot__send" type="submit">Envoyer</button>
		</form>

		<p class="bpcab-chatbot__hint">
			<em>Réponse automatique. Ne partagez pas d’informations sensibles.</em>
		</p>
	</div>
	<?php
	return (string) ob_get_clean();
}
add_shortcode('ai_chatbot', 'bpcab_ai_chatbot_shortcode');

function bpcab_rate_limit_or_die(string $ip, int $limit = 10, int $window_seconds = 60): void {
	$key = 'bpcab_rl_' . md5($ip);
	$hits = (int) get_transient($key);

	if ($hits >= $limit) {
		wp_send_json_error(array(
			'message' => 'Trop de requêtes. Attendez une minute et réessayez.'
		), 429);
	}

	set_transient($key, $hits + 1, $window_seconds);
}

function bpcab_get_ai_answer(string $user_message) {
	if (!defined('BPCAB_OPENAI_API_KEY') || BPCAB_OPENAI_API_KEY === '') {
		return new WP_Error('bpcab_missing_key', 'Clé API manquante. Ajoutez BPCAB_OPENAI_API_KEY dans wp-config.php.');
	}

	$cache_key = 'bpcab_ai_' . md5(mb_strtolower(trim($user_message)));
	$cached = get_transient($cache_key);
	if (is_string($cached) && $cached !== '') {
		return $cached;
	}

	$system = "Vous êtes un assistant pour un site WordPress. Répondez en français, de façon courte et pratique. "
		. "Si la question demande des infos sensibles (santé/juridique/finance), refusez et conseillez de contacter le propriétaire du site. "
		. "Si vous ne savez pas, dites-le et proposez une piste (ex: consulter la page FAQ).";

	$endpoint = 'https://api.openai.com/v1/chat/completions';

	$body = array(
		'model' => 'gpt-4.1-mini',
		'temperature' => 0.2,
		'messages' => array(
			array('role' => 'system', 'content' => $system),
			array('role' => 'user', 'content' => $user_message),
		),
	);

	$args = array(
		'headers' => array(
			'Authorization' => 'Bearer ' . BPCAB_OPENAI_API_KEY,
			'Content-Type'  => 'application/json',
		),
		'timeout' => 20,
		'body' => wp_json_encode($body),
	);

	$response = wp_remote_post($endpoint, $args);

	if (is_wp_error($response)) {
		return new WP_Error('bpcab_http_error', 'Erreur HTTP : ' . $response->get_error_message());
	}

	$code = (int) wp_remote_retrieve_response_code($response);
	$raw  = (string) wp_remote_retrieve_body($response);

	if ($code < 200 || $code >= 300) {
		return new WP_Error('bpcab_api_error', 'API IA indisponible (code ' . $code . '). Vérifiez votre clé/quota.');
	}

	$data = json_decode($raw, true);
	if (!is_array($data)) {
		return new WP_Error('bpcab_json_error', 'Réponse API illisible (JSON invalide).');
	}

	$text = $data['choices'][0]['message']['content'] ?? '';
	$text = is_string($text) ? trim($text) : '';

	if ($text === '') {
		return new WP_Error('bpcab_empty_answer', 'Réponse vide de l’API IA.');
	}

	$allowed = array(
		'a' => array('href' => true, 'target' => true, 'rel' => true),
		'strong' => array(),
		'em' => array(),
		'br' => array(),
	);

	$safe = wp_kses($text, $allowed);

	set_transient($cache_key, $safe, DAY_IN_SECONDS);

	return $safe;
}

function bpcab_ajax_chatbot_ask(): void {
	$nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
	if (!wp_verify_nonce($nonce, 'bpcab_chatbot_nonce')) {
		wp_send_json_error(array('message' => 'Requête refusée (nonce invalide).'), 403);
	}

	$ip = bpcab_get_client_ip();
	bpcab_rate_limit_or_die($ip, 10, 60);

	$message = isset($_POST['message']) ? (string) wp_unslash($_POST['message']) : '';
	$message = trim($message);

	if ($message === '') {
		wp_send_json_error(array('message' => 'Message vide.'), 400);
	}

	if (mb_strlen($message) > 400) {
		wp_send_json_error(array('message' => 'Message trop long (max 400 caractères).'), 400);
	}

	$answer = bpcab_get_ai_answer($message);

	if (is_wp_error($answer)) {
		wp_send_json_error(array(
			'message' => $answer->get_error_message()
		), 500);
	}

	wp_send_json_success(array('answer' => $answer));
}
add_action('wp_ajax_bpcab_chatbot_ask', 'bpcab_ajax_chatbot_ask');
add_action('wp_ajax_nopriv_bpcab_chatbot_ask', 'bpcab_ajax_chatbot_ask');

Код Пояснение

Зачем нужен mu-плагин?

Плагин mu загружается автоматически, без активации, и менее «хрупок», чем фрагмент кода, вставленный в тему оформления. По моему опыту, когда новичок вставляет код в тему, это происходит быстрее. functions.php А если затем сменить тему оформления (или обновить родительскую тему), чат-бот исчезнет.

Почему wp_localize_script()

Здесь мы используем его для переключения на JavaScript:

  • URL AJAX (admin-ajax.php)
  • нунций
  • ограничение длины

Это позволяет избежать "жесткого кодирования" URL-адреса и работает даже если WordPress установлен в подпапке.

Док: wp_localize_script ().

Nonce: что именно он защищает

Nonce в WordPress предотвращает отправку запросов от имени другого сайта через браузер посетителя (CSRF-атака). Он не заменяет систему аутентификации, но для публичного чат-бота это минимальное требование.

Док: WordPress Noces.

Временные данные кэша: почему это важно

. Перепады Это кэши типа «ключ/значение» с ограниченным сроком действия. Если посетитель спрашивает «Какое у вас время работы?» 200 раз в месяц, вы не захотите платить за 200 вызовов ИИ. Вы платите за один вызов, а затем предоставляете ответ из кэша.

Док: Временные данные API.

Очистка ответа

API искусственного интеллекта может возвращать неожиданный текст (или даже HTML). Если внедрить его непосредственно в DOM, это откроет путь к XSS-уязвимостям. Вот здесь:

  • мы разрешаем только a, strong, em, br
  • всё остальное удалено

Используемая функция: wp_kses()Док: wp_kses().

Стоимость и оптимизация API

Точные цены зависят от модели и вашего договора. Вот что важно для составления сметы:

  • количество сообщений в месяц
  • средняя длина вопросов и ответов (токенов)
  • Коэффициент попадания в кэш (количество повторений одинаковых вопросов)

Оценка "на месте" (порядок величины)

При работе с простым чат-ботом для ознакомления с работой системы я часто наблюдаю следующее:

  • Вопрос: от 20 до 60 жетонов
  • Ответ: от 60 до 200 токенов
  • Всего: от 100 до 300 токенов за взаимодействие.

Если у вас 2000 взаимодействий в месяц, это от 200 000 до 600 000 токенов в месяц. В зависимости от модели, это может быть разумно… или нет. Кэширование и «мини»-модель имеют огромное значение.

Немедленная оптимизация

  • 24-часовой тайник (уже есть). Для стабильной работы раздела часто задаваемых вопросов можно увеличить интервал до 7 дней.
  • Более короткие ответы Добавьте к запросу «ответить максимум в 5 предложениях».
  • Низкая температура Чем меньше вариантов, тем больше попаданий в кэш.
  • Ограничьте длину Сообщения на стороне JavaScript и PHP (уже настроены). Всегда двойной барьер.

Расширенные варианты и сценарии использования

Вариант 1 — Добавить «контекст сайта» (без RAG)

Не прибегая к поиску по всем статьям, вы можете предоставить краткую статическую информацию (например, часы работы, URL раздела часто задаваемых вопросов). Это полезно для демонстрационного сайта.

// Exemple : ajoutez ceci avant $body dans bpcab_get_ai_answer()
$site_context = "Contexte du site :n"
	. "- FAQ : https://example.com/faq/n"
	. "- Contact : https://example.com/contact/n"
	. "- Horaires : lun-ven 9h-18hn";

$body['messages'] = array(
	array('role' => 'system', 'content' => $system . "nn" . $site_context),
	array('role' => 'user', 'content' => $user_message),
);

Вариант 2 — Режим «Краткое содержание статьи» с помощью атрибута шорткода.

Вы можете разрешить [ai_chatbot mode="resume"] и отправить фрагмент текущей страницы ИИ. Примечание: больше токенов = выше стоимость.

// Exemple (incomplet) : détecter un attribut et injecter le contenu
function bpcab_ai_chatbot_shortcode($atts = array()): string {
	$atts = shortcode_atts(array('mode' => 'chat'), $atts, 'ai_chatbot');

	// ... même HTML qu'avant, mais vous pourriez ajouter data-mode
	// <div data-mode="chat"> etc.
	// Ce snippet est volontairement partiel : il faut adapter le JS pour envoyer le mode.
	return '...';
}

Хочу сразу уточнить: этот фрагмент кода неполный. Для его корректной работы необходимо также изменить JavaScript-код (отправить). mode) и обработчик AJAX (измените приглашение в соответствии с режимом).

Вариант 3 — интеграция Divi 5 / Elementor / Avada

  • Дива 5 Добавьте модуль «Код» или «Шорткод» и вставьте его. [ai_chatbot]Если Divi выполняет миниляцию/конкатенацию, очистите его статический кэш, если CSS/JS не обновляются.
  • Elementor виджет «Шорткод» → [ai_chatbot]Если вы используете оптимизацию ресурсов Elementor, убедитесь, что выполнение скрипта не откладывается слишком часто (в противном случае...). DOMContentLoaded Это может произойти до инъекции: в этом случае также выполняется инициализация. elementor/frontend/init).
  • Авада : Элемент «Шорткод» в Fusion Builder. Если у вас включено кэширование Avada, очистите кэш после добавления файлов CSS/JS.

Безопасность и передовой опыт

Никогда не раскрывайте ключ API на стороне клиента.

Простое правило: ключ остается внутри. wp-config.phpБраузер никогда не должен его увидеть. Даже "скрытый" в JS-пакете, он всё равно может быть восстановлен.

Проверка и ограничение количества записей

  • Максимальная длина на стороне JavaScript (UX) + на стороне PHP (безопасность).
  • Отклонять пустые сообщения.
  • Ограничение скорости по IP-адресу (уже реализовано). Для большей гибкости можно также ограничить скорость по сессии/cookie.

Приберитесь у выходов

Отобразить ответ в виде текста (как в JavaScript) textContent) или тщательно очищать, если вы разрешаете HTML. Здесь мы делаем и то, и другое: очищаем на стороне сервера и отображаем как текст на стороне клиента. Это сделано намеренно «параноично».

GDPR: данные, передаваемые третьей стороне.

Если посетитель вводит личные данные, вы потенциально отправляете их поставщику услуг (OpenAI). Планируйте свои действия соответствующим образом:

  • Чёткое сообщение («Не разглашайте конфиденциальную информацию»).
  • упоминание в вашей политике конфиденциальности
  • Настройки поставщика (сохранение данных, отказ от получения информации и т. д.) в соответствии с вашим договором.

Не проводите тестирование в рабочей среде без резервной копии.

Ошибка PHP в mu-плагине приводит к сбою всего сайта (поскольку он загружается автоматически). Сначала поработайте на тестовой копии. Если вам нужно работать в продакшене, сохраните доступ по FTP/SSH, чтобы удалить файл в случае появления белого экрана.

Как тестировать и отлаживать

Включите корректное ведение журналов.

В тестовой среде включите:

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);

Док: Отладка в WordPress.

Проверьте AJAX-запрос.

  • Откройте вкладку «Инструменты разработчика» в браузере → вкладка «Сеть» → отфильтруйте по... admin-ajax.php.
  • Проверьте HTTP-статус (200/403/429/500) и JSON-ответ.
  • Если вместо JSON вы видите HTML-страницу, это часто означает фатальную ошибку PHP (или перехват данных плагином безопасности).

Тестирование API ИИ в «пробном запуске»

Перед интеграцией JavaScript вы можете вызвать bpcab_get_ai_answer() В административном контексте (временно) запишите ответ в лог. Не оставляйте этот код без изменений.

Если это не сработает

симптом Причина вероятна проверка Решение
На странице ничего не отображается. Отсутствует или не интерпретируется короткий код. Убедитесь, что страница содержит [ai_chatbot] (не в блоке "Код", который экранирует скобки) Используйте блок «Шорткод» (Elementor) / модуль «Шорткод» (Divi/Avada).
Окно чата отображается, но кнопка не реагирует. JavaScript не загружен (кэш, путь, конфликт) Инструменты разработчика → Консоль (ошибки) + Сеть (JS-файл 404?) Проверить plugins_url()очистить кэш, увеличить BPCAB_CHATBOT_VERSION
Ответ «недействительный, отсутствует» Истек срок действия одноразового кода (nonce), агрессивное кэширование страниц или JavaScript использует старый одноразовый код. Посмотрите на значение BPCAB_CHATBOT.nonce в консоли Уменьшите размер кэша на страницах с чатом или сгенерируйте nonce заново при загрузке (более продвинутый подход).
Ошибка 429 «Слишком много запросов» Слишком строгие ограничения скорости или повторные проверки Воспроизвести в режиме приватного просмотра / с другого IP-адреса. Увеличьте размер окна/лимита или ограничьте доступ только для авторизованных пользователей.
Ошибка «API ИИ недоступен (код 401/403)» Недействительный ключ, ключ не загружен, права доступа Проверить BPCAB_OPENAI_API_KEY в wp-config.php (без пробелов) Сгенерируйте ключ заново, проверьте выставление счетов/квоту на стороне поставщика.
Ошибка «Недопустимый JSON» Усеченный ответ API (прокси, WAF) или измененная конечная точка Авторизоваться $raw на этапе (обратите внимание на данные) Настройте конечную точку/формат в соответствии с официальной документацией, при необходимости увеличьте время ожидания.
Белый экран после добавления mu-плагина Ошибка PHP (скобки, точка с запятой), слишком старая версия PHP. советоваться wp-content/debug.log или журналы сервера Исправьте синтаксис, проверьте PHP 8.1+, при необходимости удалите файл через FTP.

Реалистичные проблемы и быстрые решения

  • Код вставлен не в то место. Если вы разместили его в разделе «Пользовательский код» конструктора, он может быть отфильтрован/экранирован. Используйте вместо него mu-plugin.
  • Неподходящий зацеп : если вы зададите вопрос по JavaScript initВы рискуете загрузить его слишком рано/неправильно. Здесь мы используем wp_enqueue_scripts.
  • Конфликт кэша Некоторые кэши кэшируют HTML-код, содержащий nonce. Результат: устаревший nonce для всех. Решение: исключить страницу из кэша или реализовать маршрут, который возвращает «свежий» nonce (расширенный вариант).
  • Постоянные Это не совсем по теме, но я видел сайты, где admin_url() Фильтрация происходит из-за неправильно настроенного плагина безопасности. Если AJAX не отвечает, проверьте URL-адрес. /wp-admin/admin-ajax.php непосредственно в браузере.

Ресурсы

FAQ

Действительно ли добавление mu-плагина позволит обойтись без каких-либо дополнительных плагинов?

Вы не устанавливаете сторонний плагин с wordpress.org, но технически добавляете код в виде плагина. Это сделано намеренно: это самый чистый способ сохранить независимость кода от темы.

Могу ли я вставить код? functions.php ?

Да, но я не рекомендую этого делать. Вы рискуете потерять чат-бота при смене тем, а конструктор тем может усложнить отладку. Если вы все же решите это сделать, используйте дочернюю тему.

Зачем использовать admin-ajax.php а не REST API?

Оба варианта работают. Для новичка настройка AJAX в WordPress занимает немного времени. Если же вам нужен более современный подход, переключитесь на REST-маршрут. register_rest_route() и разрешения на обратный вызов. Ядро (вызов) wp_remote_post()(Кэш, безопасность) остаются неизменными.

Мой плагин кэширования нарушает работу nonce: что мне делать?

Исключите страницу из кэша (простое решение) или реализуйте конечную точку, которая возвращает nonce по запросу, а затем обновите JavaScript для его получения. На сайтах с большим объемом кэширования (агрессивные CDN) это классический подход.

Как ограничить доступ чат-бота к определенным страницам?

Шорткод уже предоставляет вам этот контроль: вы добавляете его только там, где это необходимо. И код запрашивает данные из JS/CSS только в том случае, если шорткод присутствует.

Как предотвратить распространение ИИ бессмысленных высказываний?

Гарантировать это на 100% невозможно. Снизьте риск:

  • краткие ответы
  • низкая температура
  • Послание ясно: «Если вы не знаете, скажите об этом».
  • Перенаправить на страницу часто задаваемых вопросов/контактной информации.

Можно ли отображать кликабельные ссылки в ответе?

Да, но делайте это осторожно. В этом уроке мы будем чистить с помощью wp_kses() разрешив aНа стороне JavaScript мы отображаем с помощью textContent (Поэтому HTML не нужен). Если вам нужны кликабельные ссылки, вам необходимо отображать их в HTML.innerHTML) и проявлять особую осторожность (строгая санитария, автоматическое добавление rel="noopener nofollow").

Совместимо ли оно с Divi 5 / Elementor / Avada?

Да: используйте модуль/виджет «Шорткод» и вставьте его. [ai_chatbot]Код не зависит от темы оформления. Единственные проблемы обычно возникают из-за кэширования/минификации: очистка после добавления файлов.

Почему вы ограничиваете текст 400 символами?

Чтобы избежать чрезмерного количества запросов, снизить затраты и ограничить злоупотребления, вы можете увеличить количество запросов, но делайте это с полным пониманием последствий (токены, задержка, выставление счетов).

Как изменить модель искусственного интеллекта?

Измените значение model в $bodyДля чат-бота-консультанта используйте "мини"-модель. При масштабировании отслеживайте затраты и задержку.

Я получаю сообщение об ошибке «HTTP Error: cURL error 28».

Тайм-аут. Проверка:

  • Доступ к сети разрешен с хост-сервера (брандмауэр).
  • DNS
  • увеличивать timeout (например, 30), если ваш сервер работает медленно.

Если у вас заблокированный тарифный план общего хостинга, это распространенная ситуация.