Ak ste niekedy dostali e-mail typu „Nenašiel som informácie na vašej stránke, môžete mi pomôcť?“, už máte dobrý príklad použitia minichatu s umelou inteligenciou. Cieľom nie je „nahradiť“ váš tím podpory, ale poskytnúť užitočnú počiatočnú odpoveď na základe obsahu vašej webovej stránky. sans inštalovať zapojiť.


Potreba / Prípad použitia

„Jednoduchý“ chatbot s umelou inteligenciou WordPress Jeho hlavným účelom je absorbovať opakujúce sa otázky: otváracie hodiny, pravidlá vrátenia tovaru, „kde nájsť taký a taký tovar“, súhrny dlhého sprievodcu alebo navigačnej pomoci („Hľadám návod na permanentné odkazy“). Na blogu som často videl, že 80 % prichádzajúcich požiadaviek sa zredukovalo na 10 otázok.

Čo prináša umelá inteligencia: minimalistické rozhranie chatu + bezpečnú trasu WordPress AJAX, ktorá odošle otázku do API rozhrania umelej inteligencie (napr. OpenAI) a vráti použiteľnú odpoveď. Zámerne sa snažíme zjednodušiť veci: žiadne widgety tretích strán, žiadne SDK, žiadne závislosti od Composera.

Nakoniec budete vedieť:

  • Zobrazenie malej mačky (HTML/CSS/JS) pomocou shortcode WordPressu.
  • Odošlite otázku na váš WordPress server (AJAX) s jednorazovým číslom (anti-CSRF token).
  • Volanie AI API s wp_remote_post(), spracovať chyby/časové limity a ukladať odpovede do vyrovnávacej pamäte pomocou prechodových javov.
  • Chráňte svoj API kľúč (uložený v wp-config.php) a vyhnite sa jeho vystaveniu prehliadaču.

Rýchle zhrnutie

  • Pridáte API konštantu do wp-config.php (nikdy nie k téme).
  • Vytvoríte mu-plugin (plugin „povinný na použitie“), ktorý poskytuje skrátený kód [ai_chatbot].
  • Front (JS) volá admin-ajax.php s jednorazovým číslom, nie priamo s rozhraním API AI.
  • Server volá API cez wp_remote_post(), krátky časový limit, čisté spracovanie chýb.
  • Vyrovnávacia pamäť odpovedí (prechodových javov) na zníženie nákladov.
  • Vyčistená odpoveď pred zobrazením (wp_kses()) na obmedzenie rizík XSS.

Kedy na to použiť umelú inteligenciu

Použite tento typ chatbota, ak:

  • Dostávate opakujúce sa otázky a chcete okamžitú „prvú odpoveď“.
  • Váš obsah je už bohatý (FAQ, stránky „O nás“, sprievodcovia) a umelá inteligencia môže používateľa viesť.
  • Akceptujete, že odpoveď môže byť niekedy nedokonalá a pridáte jasný odkaz („automatická odpoveď“).
  • Chcete ľahký, kontrolovaný chatbot bez toho, aby ste sa museli spoliehať na nepriehľadný plugin, ktorý všade vkladá externé skripty.

Často ho odporúčam pre blogy s tutoriálmi, prezentačné stránky alebo špecializované oblasti, kde sú otázky veľmi stabilné (napr. „ako rezervovať“, „lehoty“, „ceny“).

Kedy NEPOUŽÍVAŤ AI

Vyhnite sa umelej inteligencii, ak klasické riešenie WordPressu robí lepšiu, jednoduchšiu a lacnejšiu prácu:

  • Interný výskum Začnite vylepšením funkcie vyhľadávania (výňatky, kategórie, stránka „Mapa stránok“). Umelá inteligencia môže byť drahá len preto, aby vám povedala, aby ste „použili funkciu vyhľadávania“.
  • Citlivá podpora (Zdravie, právne záležitosti, financie): Riziko halucinácií je reálne. V týchto prípadoch je bezpečnejší kontaktný formulár + báza vedomostí.
  • Problémy s transakciami (príkazy, účty): ak odpoveď závisí od súkromných údajov, nevytvárajte „jednoduchý“ chat s umelou inteligenciou. Potrebujete skutočné overenie, povolenia a model hrozby.
  • Napätý rozpočet Ak máte nízku návštevnosť, je to v poriadku. Ak máte 50 000 návštev denne, náklady môžu prudko stúpnuť, ak nemáte limit vyrovnávacej pamäte/rýchlosti.

predpoklady

Cielené verzie WordPress 6.9.4 (apríl 2026) a PHP 8.1+.

Kľúč API pre AI Ako príklad používam OpenAI, pretože API je stabilné a dobre zdokumentované, ale architektúra bude rovnaká ako v Anthropic/Mistral/Google.

Rýchle pochopenie: volania API a HTTP

Une API je rozhranie, ktoré vám umožňuje niečo vyžiadať od služby (tu: „odpovedať na túto otázku“). Technicky odosielate HTTP požiadavku (často v POST) s JSON a vy na oplátku dostanete JSON.

Vo WordPresse sa to robí správne cez HTTP API: wp_remote_post() (a wp_remote_get()Toto je odporúčaná metóda, pretože spracováva transport (cURL, streamy), proxy a integruje sa s WordPressom.

Oficiálny zdroj: HTTP API pre WordPress.

Kam uložiť API kľúč (povinné)

Kľúč si uložíte do wp-config.phpNie v súbore témy a už vôbec nie v JavaScripte. Ak sa váš kľúč dostane do prehliadača, bude do 30 sekúnd skopírovaný a použitý na vaše náklady.

Pridajte toto k wp-config.php (ideálne tesne pred riadkom „To je všetko, prestaňte s úpravami!“)

define('BPCAB_OPENAI_API_KEY', 'VOTRE_CLE_ICI');

náklady Každá správa odoslaná do API stojí peniaze (za token). Aj keď ide o „pár centov“, môže sa to nasčítať. Preto implementujeme ukladanie do vyrovnávacej pamäte a limit rýchlosti na strane servera.

Kam vložiť kód

  • Odporúčaná možnosť : mu-plugin (poplatok automaticky). Cesta: wp-content/mu-plugins/.
  • Alternatíva : klasický vlastný plugin v wp-content/plugins/.
  • Vyhnúť sa : functions.php témy (najmä ak zmeníte témy alebo ak nástroj na tvorbu aktualizuje súbory).

Oficiálna dokumentácia k mu-pluginom: Pluginy, ktoré musíte použiť.

Architektúra riešenia

Tu je informačný kanál, zrozumiteľným spôsobom:

  • Návštevník otvorí stránku, ktorá obsahuje [ai_chatbot].
  • Skrátený kód zobrazuje používateľské rozhranie (malé okno) + načítava JS.
  • Keď používateľ odošle správu, JS odošle požiadavku POST. admin-ajax.php s:
    • správa
    • WordPress nonce (dopyt s ochranou proti sfalšovaniu)
  • WordPress prijme požiadavku prostredníctvom AJAX hooku, overí nonce, použije limit rýchlosti a potom zavolá AI API prostredníctvom wp_remote_post().
  • WordPress vyčistí odpoveď, uloží ju do vyrovnávacej pamäte (prechodne) a potom odošle JSON späť do prehliadača.
  • Prehliadač zobrazí odpoveď.

Prečo používať AJAX vo WordPresse (a nevolať AI API v JS)?

Pretože váš kľúč musí zostať na strane servera. Ak voláte OpenAI priamo z prehliadača, odhalíte kľúč... a stratíte kontrolu (náklady, zneužitie, scraping).

Kompletný kód – krok za krokom

Vytvoríme mu-plugin, ktorý:

  • pridať krátky kód [ai_chatbot]
  • Čisté zaradenie CSS/JS do frontu
  • vytvára AJAX akciu pre prihlásených aj odhlásených používateľov
  • volá rozhranie OpenAI API
  • ukladá odpovede do vyrovnávacej pamäte

Krok 1 – Vytvorenie pluginu mu

Vytvorte priečinok wp-content/mu-plugins/ Ak neexistuje, vytvorte súbor:

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';
}

Krok 2 – Načítanie krátkeho kódu + CSS/JS

Un shortcode je tag v zátvorkách (napr. [ai_chatbot]), ktorý WordPress nahrádza HTML. Toto je pohodlné s Divi 5, Elementor a Avada: všetky vedia, ako vložiť shortcode (modul „Code“, widget „Shortcode“ atď.).

/**
 * 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');

Častým problémom je vloženie tohto kódu do nesprávneho súboru (úryvok kódu pre builder alebo plugin úryvkov, ktorý sa spúšťa príliš neskoro). S pluginom mu sa vyhnete mnohým prekvapeniam.

Krok 3 – Vytvorenie súborov CSS/JS

V tom istom súbore mu-plugins, vytvoriť:

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

Minimálne CSS (Môžete si ho prispôsobiť téme/tvorcovi):

.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;
}

Minimálny JS Odosielanie správ do WordPressu pomocou AJAXu, zobrazovanie správ, správa stavu.

(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);
	});
})();

Častá chyba: „JS sa nenačítava.“ V 90 % prípadov je to nesprávna cesta. plugins_url()alebo vyrovnávaciu pamäť (plugin/CDN), ktorá zobrazuje starú verziu. Zmeňte verziu BPCAB_CHATBOT_VERSION Vynútenie opätovného načítania alebo vymazanie vyrovnávacej pamäte.

Krok 4 – Koncový bod AJAX + nonce + limit rýchlosti

Un háčik WordPress je vstupný bod. Existujú dva typy:

  • akčná „urobte tu niečo“ (napr. wp_enqueue_scripts).
  • filtre : „upraviť túto hodnotu“ (napr. the_content).

Tu používame akcie AJAX: wp_ajax_* (pripojené) a wp_ajax_nopriv_* (nie je pripojené).

/**
 * 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');

Krok 5 – Volanie OpenAI cez wp_remote_post() + prechodné ukladanie do vyrovnávacej pamäte

Ideme na to:

  • Vytvorte kľúč vyrovnávacej pamäte na základe otázky (hash).
  • Nastavte si TTL (napr. 24 hodín), aby ste sa vyhli opätovnému plateniu za tú istú odpoveď.
  • Pridajte krátky časový limit (napr. 20 sekúnd), aby ste zabránili „zaseknutiu“ PHP.
  • Vyčistite odpoveď (povolená je veľmi obmedzená podmnožina 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;
}

Dve chyby, ktoré často vidím:

  • Zabudnutie bodkočiarky v wp-config.php alebo v mu-plugine: výsledok, biela obrazovka (fatálna chyba). Aktivovať WP_DEBUG V štádiu prípravy, nie v produkcii.
  • Skopírujte starý tutoriál ktorý používa starú štruktúru API alebo zastaraný model. V tomto prípade máte základ WordPress 6.9.4 + PHP 8.1 a koncový bod upravujete iba v prípade, že OpenAI zmení svoje odporúčanie.

Kompletný zostavený kód

Nižšie je uvedený kompletný súbor mu-pluginBudete tiež musieť vytvoriť súbory CSS/JS (zobrazené vyššie). Kľúč do tohto súboru nevkladajte.

<?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');

Vysvetlenie kódu

Prečo mu-plugin?

MU-plugin sa načíta automaticky, bez aktivácie, a je menej „krehký“ ako úryvok kódu vložený do témy. Podľa mojej skúsenosti, keď začiatočník vloží kód do functions.php a potom zmení tému (alebo aktualizuje nadradenú tému), chatbot zmizne.

Pourquoi wp_localize_script()

Tu ho používame na prepnutie do JS:

  • URL adresa AJAX (admin-ajax.php)
  • nuncius
  • obmedzenie dĺžky

Tým sa zabráni „pevnému kódovaniu“ URL adresy a funguje to, aj keď je WordPress nainštalovaný v podpriečinku.

Dokument: wp_localize_script ().

Nonce: čo to vlastne chráni

Jednoduchý kód WordPressu bráni inej stránke v odosielaní požiadaviek vo vašom mene prostredníctvom prehliadača vášho návštevníka (útok CSRF). Nenahrádza autentifikačný systém, ale pre verejného chatbota je to minimálna požiadavka.

Dokument: Nonce vo WordPresse.

Prechodové javy vyrovnávacej pamäte: Prečo je to dôležité

Les transients Ide o vyrovnávaciu pamäť kľúč/hodnota s dátumom expirácie. Ak sa návštevník 200-krát za mesiac opýta „Aká je vaša pracovná doba?“, nechcete platiť za 200 volaní umelej inteligencie. Zaplatíte za jedno volanie a potom zobrazíte odpoveď z vyrovnávacej pamäte.

Dokument: Prechodné javy API.

Čistenie reakcie

API rozhranie AI môže vrátiť neočakávaný text (alebo dokonca HTML). Ak ho vložíte priamo do DOM, otvoríte dvere zraniteľnostiam XSS. Tu:

  • povoľujeme iba a, strong, em, br
  • všetko ostatné je vymazané

Použitá funkcia: wp_kses()Dokument: wp_kses().

Náklady na API a optimalizácia

Presné ceny závisia od modelu a vašej zmluvy. Tu je to, čo je dôležité pre odhad:

  • počet správ za mesiac
  • priemerná dĺžka otázok a odpovedí (tokeny)
  • miera úspešnosti vo vyrovnávacej pamäti (koľko identických otázok sa opakuje)

Odhad „na mieste“ (rádová hodnota)

Na jednoduchom „orientačnom“ chatbote často pozorujem:

  • Otázka: 20 až 60 žetónov
  • Odpoveď: 60 až 200 žetónov
  • Celkom: 100 až 300 tokenov na interakciu

Ak máte 2 000 interakcií mesačne, je to 200 000 až 600 000 tokenov mesačne. V závislosti od modelu to môže byť rozumné... alebo nie. Ukladanie do vyrovnávacej pamäte a „mini“ model majú obrovský význam.

Okamžité optimalizácie

  • 24-hodinová keška (už zavedené). Pre stabilné FAQ môžete interval predĺžiť na 7 dní.
  • Kratšie odpovede doplniť k výzve „odpovedať maximálne v 5 vetách“.
  • Nízka teplota Menej variácií, viac zásahov do vyrovnávacej pamäte.
  • Obmedziť dĺžku Správy na strane JS aj PHP (už sú zavedené). Vždy dvojitá bariéra.

Pokročilé varianty a prípady použitia

Variant 1 – Pridanie „kontextu lokality“ (bez RAG)

Bez prehľadávania článkov môžete poskytnúť stručný statický kontext (napr. otváracie hodiny, URL adresu často kladených otázok). Toto je užitočné pre prezentačnú webovú stránku.

// 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),
);

Variant 2 – Režim „Zhrnutie článku“ prostredníctvom atribútu shortcode

Môžete povoliť [ai_chatbot mode="resume"] a odoslať aktuálny výpis stránky umelej inteligencii. Poznámka: viac tokenov = vyššie náklady.

// 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 '...';
}

Aby som to uviedol na pravú mieru: tento úryvok je neúplný. Aby fungoval, je potrebné upraviť aj JavaScript (odoslať mode) a obslužný program AJAX (zmeniť výzvu podľa režimu).

Variant 3 — Divi 5 / Elementor / integrácia Avada

  • Divi 5 Pridajte modul „Kód“ alebo „Krátky kód“ a vložte ho [ai_chatbot]Ak Divi minifikuje/zreťazuje, vymažte jeho statickú vyrovnávaciu pamäť, ak sa CSS/JS neaktualizuje.
  • Elementor widget „Krátky kód“ → [ai_chatbot]Ak používate optimalizáciu aktív Elementoru, skontrolujte, či skript nie je agresívne odložený (inak DOMContentLoaded môže sa stať pred injekciou: v tomto prípade inicializujte aj na elementor/frontend/init).
  • Avada Prvok „Shortcode“ vo Fusion Builderi. Ak máte ukladanie do vyrovnávacej pamäte Avada, po pridaní súborov CSS/JS ich vymažte.

Bezpečnosť a osvedčené postupy

Nikdy nezverejňujte kľúč API na strane klienta

Jednoduché pravidlo: kľúč zostáva vnútri wp-config.phpPrehliadač by ho nikdy nemal vidieť. Aj keď je „skrytý“ v JS balíku, je stále obnoviteľný.

Overiť a obmedziť položky

  • Maximálna dĺžka na strane JS (UX) + na strane PHP (bezpečnosť).
  • Odmietnuť prázdne správy.
  • Obmedzenie rýchlosti podľa IP adresy (už je zavedené). Ak chcete ísť ďalej, môžete obmedziť aj podľa relácie/súboru cookie.

Vyčistite východy

Zobraziť odpoveď ako text (ako v JS s textContent) alebo dôkladne dezinfikujeme, ak povolíte HTML. Tu robíme oboje: dezinfikujeme na strane servera a zobrazujeme ako text na strane klienta. Toto je zámerne „paranoidné“.

GDPR: údaje odoslané tretej strane

Ak návštevník zadá osobné údaje, potenciálne ich odosielate poskytovateľovi služieb (OpenAI). Naplánujte si to podľa toho:

  • jasný odkaz („Nezdieľajte citlivé informácie.“)
  • zmienka vo vašich zásadách ochrany osobných údajov
  • Nastavenia dodávateľa (udržiavanie, odhlásenie atď.) podľa vašej zmluvy

Netestujte v produkcii bez zálohy.

MU-plugin s chybou PHP pokazí celú stránku (pretože sa načítava automaticky). Najprv pracujte na testovacej kópii. Ak potrebujete pracovať v produkčnom prostredí, udržujte prístup cez FTP/SSH, aby ste mohli súbor odstrániť v prípade bielej obrazovky.

Ako testovať a ladiť

Správne povoliť protokoly

V testovacom prostredí povoľte:

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

Dokument: Ladenie vo WordPresse.

Skontrolujte požiadavku AJAX

  • Otvorte v prehliadači DevTools → kartu Sieť → zapnite filter admin-ajax.php.
  • Skontrolujte stav HTTP (200/403/429/500) a odpoveď JSON.
  • Ak sa namiesto JSON zobrazí HTML stránka, často ide o fatálnu chybu PHP (alebo bezpečnostný doplnok, ktorý zachytáva stránku).

Testovanie AI API „na suchu“

Pred integráciou JS môžete zavolať bpcab_get_ai_answer() v administrátorskom kontexte (dočasne) a zaznamenať odpoveď. Tento kód nenechávajte na mieste.

Ak to nefunguje

symptóm Príčina pravdepodobná overenie Riešenie
Na stránke sa nič nezobrazuje Chýbajúci alebo neinterpretovaný skrátený kód Skontrolujte, či stránka obsahuje [ai_chatbot] (nie v bloku „Kód“, ktorý escape zátvorky) Použite blok „Shortcode“ (Elementor) / modul Shortcode (Divi/Avada)
Okno chatu sa zobrazí, ale tlačidlo nereaguje JavaScript sa nenačítal (vyrovnávacia pamäť, cesta, konflikt) Nástroje pre vývojárov → Konzola (chyby) + Sieť (JS súbor 404?) check plugins_url()vymazať vyrovnávaciu pamäť, prírastok BPCAB_CHATBOT_VERSION
Odpoveď „neplatná žiadna“ Platnosť nonce vypršala, agresívna vyrovnávacia pamäť stránky alebo JS prijíma starý nonce Pozrite sa na hodnotu BPCAB_CHATBOT.nonce v konzole Znížte vyrovnávaciu pamäť na stránkach s chatom alebo regenerujte nonce pri načítaní (pokročilejší prístup)
Chyba 429 „Príliš veľa požiadaviek“ Príliš prísne limity frekvencií alebo opakované testovanie Reprodukovať v súkromnom prehliadaní / s inou IP adresou Zväčšiť okno/limit alebo obmedziť na prihlásených používateľov
Chyba „Rozhranie API umelej inteligencie nie je k dispozícii (kód 401/403)“ Neplatný kľúč, kľúč nebol načítaný, povolenia check BPCAB_OPENAI_API_KEY v wp-config.php (bez medzier) Znovu vygenerujte kľúč, skontrolujte fakturáciu/kvótu na strane dodávateľa
Chyba „Neplatný JSON“ Skrátená odpoveď API (proxy, WAF) alebo zmenený koncový bod Prihlásiť sa $raw v štádiu (všimnite si údaje) Upravte koncový bod/formát podľa oficiálnej dokumentácie, v prípade potreby predĺžte časový limit.
Biela obrazovka po pridaní pluginu mu Chyba PHP (zátvorka, bodkočiarka), verzia PHP je príliš stará konzultovať wp-content/debug.log alebo protokoly servera Opravte syntax, skontrolujte PHP 8.1+, v prípade potreby súbor vymažte cez FTP

Realistické problémy a rýchle riešenia

  • Kód vložený na nesprávne miesto Ak ste ho umiestnili do sekcie „Vlastný kód“ v nástroji na tvorbu, môže byť filtrovaný/escaped. Namiesto toho použite mu-plugin.
  • Nevhodný háčik : ak zadáte dotaz na JS na initRiskujete, že ho načítate príliš skoro/nesprávne. Tu používame wp_enqueue_scripts.
  • Konflikt vyrovnávacej pamäte Niektoré vyrovnávacie pamäte ukladajú HTML kód obsahujúci nonce. Výsledok: zastaraný nonce pre všetkých. Riešenie: vylúčte stránku z vyrovnávacej pamäte alebo implementujte trasu, ktorá vracia „nový“ nonce (pokročilý variant).
  • permalinks Nesúvisí to priamo, ale videl som stránky, kde admin_url() je filtrované nesprávne nakonfigurovaným bezpečnostným doplnkom. Ak AJAX nereaguje, otestujte URL adresu /wp-admin/admin-ajax.php priamo v prehliadači.

zdroje

Často kladené otázky

Je to naozaj „bez pluginov“, ak pridám mu-plugin?

Neinštalujete plugin tretej strany z wordpress.org, ale technicky pridávate kód vo forme pluginu. Toto je zámerné: je to najčistejší spôsob, ako udržať kód nezávislý od témy.

Môžem vložiť kód functions.php ?

Áno, ale neodporúčam to. Pri zmene tém riskujete stratu chatbota a nástroj na tvorbu tém môže skomplikovať ladenie. Ak to aj tak urobíte, použite podradenú tému.

Prečo používať admin-ajax.php namiesto REST API?

Obe fungujú. Pre začiatočníka sa AJAX WordPress rýchlo nastavuje. Ak chcete modernejší prístup, prejdite na REST trasu s register_rest_route() a oprávnenia na spätné volanie. Jadro (volanie wp_remote_post()(vyrovnávacia pamäť, zabezpečenie) zostáva rovnaké.

Kód nonce sa pokazí s mojím doplnkom pre ukladanie do vyrovnávacej pamäte: čo mám robiť?

Vylúčte stránku z vyrovnávacej pamäte (jednoduché riešenie) alebo implementujte koncový bod, ktorý na požiadanie vráti hodnotu nonce, a potom aktualizujte JS, aby ju načítal. Na stránkach s vysokou mierou vyrovnávacej pamäte (agresívna CDN) je to klasický prístup.

Ako môžem obmedziť chatbota na určité stránky?

Shortcode vám už poskytuje túto kontrolu: pridáte ho iba tam, kde ho potrebujete. A kód sa dotazuje na JS/CSS iba v prípade, že je shortcode prítomný.

Ako môžeme zabrániť umelej inteligencii v šírení nezmyslov?

Nemôžete to zaručiť na 100 %. Znížte riziko:

  • krátke odpovede
  • nízka teplota
  • Odkaz je jasný: „Ak nevieš, povedz to.“
  • Presmerovanie na vaše najčastejšie otázky/kontakt

Môžem v odpovedi zobraziť klikateľné odkazy?

Áno, ale robte to opatrne. V tomto návode si to vyčistíme pomocou wp_kses() autorizáciou aNa strane JS zobrazujeme pomocou textContent (takže žiadny HTML). Ak chcete klikateľné odkazy, musíte ich zobraziť v HTML (innerHTML) a postupovať s mimoriadnou opatrnosťou (prísna hygiena, automatické pridávanie rel="noopener nofollow").

Je kompatibilný s Divi 5 / Elementor / Avada?

Áno: použite modul/widget „Shortcode“ a vložte ho [ai_chatbot]Kód je nezávislý od témy. Jediné problémy zvyčajne vznikajú pri ukladaní do vyrovnávacej pamäte/minifikacii: vymazanie po pridaní súborov.

Prečo to obmedzujete na 400 znakov?

Aby ste sa vyhli obrovskému počtu výziev, znížili náklady a obmedzili zneužívanie, môžete ich počet zvýšiť, ale urobte tak s plným vedomím dôsledkov (tokeny, latencia, fakturácia).

Ako zmeniť model umelej inteligencie?

Zmeňte hodnotu model v $bodyPonechajte si „mini“ model pre chatbota s pokynmi. Ak sa rozširujete, monitorujte náklady a latenciu.

Zobrazuje sa mi chyba „HTTP chyba: cURL chyba 28“

Je to časový limit. Skontrolujte:

  • Prístup k sieti povolený z hostiteľského servera (firewall)
  • DNS
  • zvýšenie timeout (napr. 30), ak je váš server pomalý

Ak máte uzamknutý zdieľaný hostingový plán, je to bežné.