# ai-builder/2.1.5/assets/js/chat-widget.js

AI Builder – Generate pages, blocks, images &amp; translate with AI, version 2.1.5. 1,207 lines.

- Page: https://pluginprobe.com/plugins/ai-builder/2.1.5/code/assets/js/chat-widget.js
- Raw: https://pluginprobe.com/plugins/ai-builder/2.1.5/raw/assets/js/chat-widget.js
- Modified: 2025-11-27T15:59:56+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/ai-builder/2.1.5/code/assets/js/chat-widget.js#L10-L20`.

```javascript
// Charger la configuration globale de l'API
const script = document.createElement("script");
// script.src = "/wp-content/plugins/ai-builder/config.js";
document.head.appendChild(script);

// Initialiser Sentry pour le widget si disponible
(function initSentry() {
  const SENTRY_SRC = "https://js-de.sentry-cdn.com/a9e1c731a49a9ab9736399318271e322.min.js";
  if (window.__aiBuilderSentry) {
    return;
  }

  try {
    const sentryScript = document.createElement("script");
    sentryScript.src = SENTRY_SRC;
    sentryScript.crossOrigin = "anonymous";
    sentryScript.onload = () => {
      window.__aiBuilderSentry = true;
      console.info("AI Builder: Sentry loaded");
    };
    sentryScript.onerror = () => {
      console.warn("AI Builder: Impossible de charger Sentry");
    };
    document.head.appendChild(sentryScript);


  } catch (error) {
    console.warn("AI Builder: Erreur lors de l'initialisation de Sentry", error);
  }
})();


// Récupération des variables injectées par wp_localize_script (admin)
if (typeof window.aiBuilderVars !== "undefined") {
  // Forcer l'utilisation d'une URL relative pour éviter les erreurs CORS
  window.ajaxurl = '/wp-admin/admin-ajax.php';
  window.aiBuilderNonce = window.aiBuilderVars.nonce;
} else {
  // Fallback pour éviter les erreurs CORS
  window.ajaxurl = '/wp-admin/admin-ajax.php';
  window.aiBuilderNonce = '';
}

document.addEventListener("DOMContentLoaded", () => {
  // Charger le CSS des témoignages
  const style = document.createElement("link");
  style.rel = "stylesheet";
  style.href = "/wp-content/plugins/ai-builder/assets/css/cards.css";
  document.head.appendChild(style);

  document.body.insertAdjacentHTML(
    "beforeend",
    `
      <div id="chat-toggle">🤖 AI Builder</div>
      <div id="chat-box">
        <div id="chat-header">
          <div id="chat-header-left">
            <h3>AI Page Builder</h3>
            <p class="warning-text">⚠️ The current page content will be replaced by AI-generated content</p>
          </div>
          <button id="css-edit-button" style="display: none;" title="Edit CSS">
            <img src="/wp-content/plugins/ai-builder/assets/images/css-edit-icon.png" alt="Edit CSS" width="16" height="16">
          </button>
        </div>
        <div id="chat-messages"></div>
        <div id="chat-input">
          <textarea placeholder="Create a modern pricing page with 3 plans and call-to-action buttons..." rows="2"></textarea>
          <button>Generate</button>
          <button id="chat-undo" style="display: none;">Undo</button>
        </div>
      </div>
      <!-- Modale CSS -->
      <div id="css-modal" style="display: none;">
        <div id="css-modal-content">
          <div id="css-modal-header">
            <h3>Edit CSS</h3>
            <button id="css-modal-close">&times;</button>
          </div>
          <div id="css-modal-tabs">
            <button id="css-tab-page" class="css-tab active">Page CSS</button>
            <button id="css-tab-blocks" class="css-tab">Blocks CSS</button>
          </div>
          <div id="css-modal-body">
            <textarea id="css-editor" placeholder="Enter your custom CSS here..."></textarea>
          </div>
          <div id="css-modal-footer">
            <button id="css-save">Save CSS</button>
            <button id="css-cancel">Cancel</button>
          </div>
        </div>
      </div>
      
    `
  );

  const toggle = document.getElementById("chat-toggle");
  const box = document.getElementById("chat-box");
  const messages = document.getElementById("chat-messages");
  const input = document.querySelector("#chat-input textarea");
  const sendBtn = document.querySelector("#chat-input button");
  const undoBtn = document.getElementById("chat-undo");
  let chatHistory = [];

  function getChatStorageKey() {
    const prefix = "aiBuilderChatHistory";
    const postId =
      wp?.data?.select("core/editor")?.getCurrentPostId?.() ||
      new URL(window.location.href, window.location.origin).searchParams.get("post") ||
      new URL(window.location.href, window.location.origin).searchParams.get("postId") ||
      new URL(window.location.href, window.location.origin).searchParams.get("p");
    if (postId) {
      return `${prefix}_post_${postId}`;
    }
    const patternName = getPatternName();
    if (patternName) {
      return `${prefix}_pattern_${patternName}`;
    }
    return `${prefix}_${window.location.pathname}`;
  }

  // Fonction pour migrer l'historique de post-new.php vers l'ID du post
  function migrateChatHistoryFromNewPost() {
    if (!window.localStorage) return;

    const prefix = "aiBuilderChatHistory";
    const newPostKey = `${prefix}_/wp-admin/post-new.php`;
    const currentKey = getChatStorageKey();

    // Si on est déjà sur la clé post-new.php, pas besoin de migrer
    if (currentKey === newPostKey) return;

    // Si la clé actuelle est basée sur un ID de post, vérifier s'il y a un historique à migrer
    if (currentKey.startsWith(`${prefix}_post_`)) {
      try {
        const oldHistory = window.localStorage.getItem(newPostKey);
        if (oldHistory) {
          const parsedHistory = JSON.parse(oldHistory);
          if (Array.isArray(parsedHistory) && parsedHistory.length > 0) {
            // Fusionner avec l'historique existant (s'il y en a un)
            const existingHistory = chatHistory.length > 0 ? chatHistory : [];
            chatHistory = [...existingHistory, ...parsedHistory];

            // Sauvegarder avec la nouvelle clé
            window.localStorage.setItem(currentKey, JSON.stringify(chatHistory));

            // Supprimer l'ancienne clé
            window.localStorage.removeItem(newPostKey);

            console.log("AI Builder: Chat history migrated from post-new.php to post ID");

            // Re-rendre l'historique
            renderChatHistory();
          }
        }
      } catch (error) {
        console.warn("AI Builder: Error migrating chat history", error);
      }
    }
  }

  // Éléments CSS
  const cssEditButton = document.getElementById("css-edit-button");
  const cssModal = document.getElementById("css-modal");
  const cssEditor = document.getElementById("css-editor");
  const cssSaveBtn = document.getElementById("css-save");
  const cssCancelBtn = document.getElementById("css-cancel");
  const cssModalClose = document.getElementById("css-modal-close");

  // Éléments des onglets
  const cssTabPage = document.getElementById("css-tab-page");
  const cssTabBlocks = document.getElementById("css-tab-blocks");

  toggle.onclick = () => {
    const isOpening = box.style.display !== "flex";
    box.style.display = box.style.display === "flex" ? "none" : "flex";
    box.style.flexDirection = "column";

    // Scroller vers le bas quand on ouvre le chat
    if (isOpening && messages) {
      // Petit délai pour s'assurer que le DOM est mis à jour
      setTimeout(() => {
        messages.scrollTop = messages.scrollHeight;
      }, 100);
    }
  };

  // Logique pour la modale CSS
  let currentCSSContent = "";
  let currentActiveTab = "page";

  // Initialiser les variables CSS globales
  window.aiBuilderPageCSS = window.aiBuilderPageCSS || "";
  window.aiBuilderBlockCSS = window.aiBuilderBlockCSS || "";

  function saveChatHistory() {
    if (!window.localStorage) return;
    try {
      const key = getChatStorageKey();
      window.localStorage.setItem(key, JSON.stringify(chatHistory));
    } catch (error) {
      console.warn("AI Builder: Unable to persist chat history", error);
    }
  }

  function loadChatHistoryFromStorage() {
    if (!window.localStorage) return;
    try {
      const key = getChatStorageKey();
      console.log("Loading chat history from storage:", key);
      const storedHistory = window.localStorage.getItem(key);
      if (!storedHistory) return;
      const parsedHistory = JSON.parse(storedHistory);
      if (Array.isArray(parsedHistory)) {
        chatHistory = parsedHistory;
      }
    } catch (error) {
      console.warn("AI Builder: Unable to read chat history", error);
    }
  }

  function renderChatHistory() {
    if (!messages) return;
    messages.innerHTML = "";
    chatHistory.forEach(({ type, message }) => {
      addMessage(message, type, { persist: false });
    });
    // Scroller vers le bas après avoir rendu l'historique
    if (chatHistory.length > 0) {
      setTimeout(() => {
        messages.scrollTop = messages.scrollHeight;
      }, 50);
    }
  }

  function getConversationHistoryString(limit = 4) {
    if (!chatHistory.length) return "";
    const recentMessages = chatHistory.slice(-limit);
    const segments = recentMessages.map(({ type, message }) => {
      const label = type === "user" ? "User question" : "AI Response";
      return `${label} : ${message}`;
    });
    return segments.join(". ") + (segments.length ? "." : "");
  }

  // Fonction pour ajouter des messages dans le chat
  function addMessage(message, type = "assistant", options = {}) {
    const { persist = true } = options;
    if (!messages) return;

    const messageDiv = document.createElement("div");
    messageDiv.className = type === "assistant" ? "ai-message" : "user-message";

    if (type === "assistant") {
      messageDiv.innerHTML = `<strong>🤖</strong> ${message}`;
    } else {
      messageDiv.innerHTML = `<strong>👤</strong> ${message}`;
    }

    messages.appendChild(messageDiv);
    messages.scrollTop = messages.scrollHeight;

    if (persist) {
      chatHistory.push({ type, message });
      saveChatHistory();
    }
  }

  loadChatHistoryFromStorage();
  renderChatHistory();

  // Migrer l'historique de post-new.php vers l'ID du post si nécessaire
  migrateChatHistoryFromNewPost();

  // Surveiller les changements d'ID de post (pour les drafts créés après le chargement)
  let lastKnownPostId = null;
  let lastKnownKey = getChatStorageKey();

  function checkPostIdChange() {
    try {
      const currentPostId = wp?.data?.select("core/editor")?.getCurrentPostId?.();
      const currentKey = getChatStorageKey();

      if (currentPostId && currentPostId !== lastKnownPostId) {
        // L'ID a changé, mettre à jour la clé de stockage
        if (currentKey !== lastKnownKey && window.localStorage) {
          // Migrer l'historique vers la nouvelle clé
          const oldHistory = window.localStorage.getItem(lastKnownKey);
          if (oldHistory) {
            try {
              const parsedHistory = JSON.parse(oldHistory);
              if (Array.isArray(parsedHistory) && parsedHistory.length > 0) {
                chatHistory = parsedHistory;
                window.localStorage.setItem(currentKey, oldHistory);
                // Ne pas supprimer l'ancienne clé immédiatement, au cas où
                console.log("AI Builder: Chat history migrated to new post ID:", currentPostId);
                renderChatHistory();
              }
            } catch (e) {
              console.warn("AI Builder: Error migrating history on ID change", e);
            }
          }
        }

        lastKnownPostId = currentPostId;
        lastKnownKey = currentKey;
      } else if (currentKey !== lastKnownKey) {
        // La clé a changé même si l'ID n'a pas changé (changement d'URL)
        lastKnownKey = currentKey;
        // Recharger l'historique avec la nouvelle clé
        loadChatHistoryFromStorage();
        renderChatHistory();
        migrateChatHistoryFromNewPost();
      }
    } catch (e) {
      // Ignorer les erreurs si wp.data n'est pas encore disponible
    }
  }

  // Vérifier l'ID initial
  setTimeout(() => {
    try {
      lastKnownPostId = wp?.data?.select("core/editor")?.getCurrentPostId?.();
    } catch (e) { }
  }, 1000);

  // Surveiller les changements d'ID toutes les 2 secondes
  setInterval(checkPostIdChange, 2000);

  // Ouvrir la modale CSS
  cssEditButton.onclick = () => {
    cssModal.style.display = "flex";
    // Charger le CSS de page par défaut
    loadCSSForTab("page");
    cssEditor.focus();
  };

  // Fermer la modale CSS
  function closeCSSModal() {
    cssModal.style.display = "none";
  }

  // Fonction pour charger le CSS selon l'onglet sélectionné
  function loadCSSForTab(tabType) {
    currentActiveTab = tabType;

    // Mettre à jour les onglets actifs
    document
      .querySelectorAll(".css-tab")
      .forEach((tab) => tab.classList.remove("active"));
    document.getElementById(`css-tab-${tabType}`).classList.add("active");

    // Charger le bon CSS
    switch (tabType) {
      case "page":
        cssEditor.value = window.aiBuilderPageCSS || "";
        console.log(
          "Loading page CSS:",
          (window.aiBuilderPageCSS || "").length,
          "chars"
        );
        break;
      case "blocks":
        cssEditor.value = window.aiBuilderBlockCSS || "";
        console.log(
          "Loading blocks CSS:",
          (window.aiBuilderBlockCSS || "").length,
          "chars"
        );
        break;
    }
  }

  // Gestion des onglets
  cssTabPage.onclick = () => loadCSSForTab("page");
  cssTabBlocks.onclick = () => loadCSSForTab("blocks");

  cssModalClose.onclick = closeCSSModal;
  cssCancelBtn.onclick = closeCSSModal;

  // Sauvegarder le CSS
  cssSaveBtn.onclick = async () => {
    const newCSSContent = cssEditor.value;

    // Déterminer le type de CSS à sauvegarder selon l'onglet actif
    let cssType = "page";
    if (currentActiveTab === "page") {
      cssType = "page";
      window.aiBuilderPageCSS = newCSSContent;
    } else if (currentActiveTab === "blocks") {
      cssType = "block";
      window.aiBuilderBlockCSS = newCSSContent;
    }

    // Sauvegarder dans les meta du post
    await saveCSSInPostMeta(newCSSContent, cssType);

    // Recharger le CSS combiné depuis le serveur après sauvegarde
    await loadCSSFromPostMeta();

    closeCSSModal();

    // Afficher un message de confirmation
    addMessage("CSS saved successfully!", "assistant");
  };

  // Fermer la modale en cliquant à l'extérieur
  cssModal.onclick = (e) => {
    if (e.target === cssModal) {
      closeCSSModal();
    }
  };



  function buildBlock(block) {
    const { blockName, attrs = {}, innerBlocks = [] } = block;
    return wp.blocks.createBlock(
      blockName,
      attrs,
      innerBlocks.map(buildBlock) // récursivité ici
    );
  }

  // Utilitaire pour récupérer le token JWT via AJAX WordPress
  async function getJwtToken() {
    if (!window.ajaxurl || !window.aiBuilderNonce) {
      console.warn("AI Builder: Missing AJAX URL or nonce");
      showAIMissingAccountToast();
      throw new Error("Missing AJAX URL or nonce");
    }

    try {
      const res = await fetch(window.ajaxurl, {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: `action=aibui_get_token&nonce=${window.aiBuilderNonce}`,
      });

      if (!res.ok) {
        throw new Error(`HTTP error! status: ${res.status}`);
      }

      const data = await res.json();
      console.log("token res: ", data);
      if (data.success && data.data.token) {
        return data.data.token;
      }
      showAIMissingAccountToast();
      throw new Error(
        "You need to have an account and be logged in to use AI features."
      );
    } catch (error) {
      console.error("Error fetching JWT token:", error);
      showAIMissingAccountToast();
      throw error;
    }
  }

  // Fonction utilitaire pour afficher un toast UX si l'utilisateur n'a pas de compte/connexion
  function showAIMissingAccountToast() {
    if (document.getElementById("ai-missing-account-toast")) return; // Pas de doublon
    const toast = document.createElement("div");
    toast.id = "ai-missing-account-toast";
    toast.innerHTML = `
      <div style="display:flex;align-items:center;justify-content:space-between;gap:16px;max-width:420px;width:90vw;background:#23272f;color:#fff;padding:18px 24px;border-radius:12px;box-shadow:0 4px 24px rgba(0,0,0,0.18);font-size:16px;position:fixed;left:50%;bottom:32px;transform:translateX(-50%);z-index:99999;flex-wrap:wrap;">
        <span style='flex:1 1 200px;min-width:180px;'>You need to have an account and be logged in to use AI features.</span>
        <a href='/wp-admin/admin.php?page=aibui-assistant' style='background:#00b87c;color:#fff;padding:8px 18px;border-radius:8px;text-decoration:none;font-weight:600;transition:background 0.2s;' target='_blank'>Go to Account</a>
      </div>
    `;
    document.body.appendChild(toast);
    setTimeout(() => {
      if (toast.parentNode) toast.parentNode.removeChild(toast);
    }, 8000);
  }

  function showNotEnoughCreditsToast() {
    if (document.getElementById("ai-missing-account-toast")) return; // Pas de doublon
    const toast = document.createElement("div");
    toast.id = "ai-missing-account-toast";
    toast.innerHTML = `
      <div style="display:flex;align-items:center;justify-content:space-between;gap:16px;max-width:420px;width:90vw;background:#23272f;color:#fff;padding:18px 24px;border-radius:12px;box-shadow:0 4px 24px rgba(0,0,0,0.18);font-size:16px;position:fixed;left:50%;bottom:32px;transform:translateX(-50%);z-index:99999;flex-wrap:wrap;">
        <span style='flex:1 1 200px;min-width:180px;'>You don't have enough credits.</span>
        <a href='/wp-admin/admin.php?page=aibui-credits' style='background:#00b87c;color:#fff;padding:8px 18px;border-radius:8px;text-decoration:none;font-weight:600;transition:background 0.2s;' target='_blank'>Go to credits page</a>
      </div>
    `;
    document.body.appendChild(toast);
    setTimeout(() => {
      if (toast.parentNode) toast.parentNode.removeChild(toast);
    }, 8000);
  }

  // Fonction pour afficher le nombre de crédits sous le titre ET sous le bouton
  function updateCreditsDisplay(credits) {
    // Sous le titre du chat
    let creditsElem = document.getElementById("ai-credits-display");
    if (!creditsElem) {
      const header = document.querySelector("#chat-header-left");
      if (header) {
        creditsElem = document.createElement("div");
        creditsElem.id = "ai-credits-display";
        creditsElem.style.fontSize = "12px";
        creditsElem.style.color = "#cccccc";
        creditsElem.style.marginTop = "2px";
        creditsElem.style.textAlign = "center";
        header.appendChild(creditsElem);
      }
    }
    if (creditsElem) {
      creditsElem.textContent =
        credits !== null ? `${credits} credits left` : "- credits left";
    }

    // À l'intérieur du bouton toggle
    const toggle = document.getElementById("chat-toggle");
    if (toggle) {
      let span = toggle.querySelector(".ai-credits-inside");
      if (!span) {
        span = document.createElement("span");
        span.className = "ai-credits-inside";
        span.style.display = "block";
        span.style.fontSize = "10px";
        span.style.color = "#cccccc";
        span.style.marginTop = "0px";
        span.style.textAlign = "center";
        toggle.appendChild(span);
      }
      span.textContent = credits !== null ? `${credits} credits` : "- credits";
    }
  }

  window.updateAICreditsDisplay = updateCreditsDisplay;

  // Fonction pour charger les crédits utilisateur
  async function loadUserCredits() {
    try {
      const jwtToken = await getJwtToken();
      const res = await fetch(window.config.apiUrl + "/user/profile", {
        method: "GET",
        headers: {
          Authorization: `Bearer ${jwtToken}`,
          "Content-Type": "application/json",
        },
      });
      if (!res.ok) throw new Error("Failed to load profile");
      const data = await res.json();
      const aiCredits = data.user?.aiCredits || {};
      const totalCredits =
        (aiCredits.onAccountCreation || 0) +
        (aiCredits.monthlySubscription || 0) +
        (aiCredits.paid || 0);
      console.log("Credits loaded:", totalCredits, "from:", aiCredits);
      updateCreditsDisplay(totalCredits);
    } catch (e) {
      console.error("Error loading credits:", e);
      updateCreditsDisplay(null);
    }
  }

  // Fonction pour injecter le CSS dans l'éditeur WordPress
  function injectCSSInEditor(cssContent) {
    // Créer un style tag pour l'éditeur
    const styleId = "ai-builder-editor-css";
    let styleElement = document.getElementById(styleId);

    if (!styleElement) {
      styleElement = document.createElement("style");
      styleElement.id = styleId;
      styleElement.type = "text/css";
      document.head.appendChild(styleElement);
    }

    styleElement.textContent = cssContent;
  }

  // Fonction pour injecter le CSS dans le frontend
  function injectCSSInFrontend(cssContent) {
    // Créer un style tag pour le frontend
    const styleId = "ai-builder-frontend-css";
    let styleElement = document.getElementById(styleId);

    if (!styleElement) {
      styleElement = document.createElement("style");
      styleElement.id = styleId;
      styleElement.type = "text/css";
      document.head.appendChild(styleElement);
    }

    styleElement.textContent = cssContent;
  }

  // Fonction pour sauvegarder le CSS dans les meta du post via AJAX WordPress
  async function saveCSSInPostMeta(cssContent, cssType = "page") {
    try {
      const postId = wp.data.select("core/editor").getCurrentPostId();

      const formData = new FormData();
      formData.append("action", "aibui_save_post_css");
      formData.append("nonce", window.aiBuilderNonce);
      formData.append("post_id", postId);
      formData.append("css_content", cssContent);
      formData.append("css_type", cssType);

      await fetch(window.ajaxurl, {
        method: "POST",
        body: formData,
      });
    } catch (err) {
      console.error("Error saving CSS to post meta:", err);
    }
  }



  // Set the meta description field by id once (no retry)
  async function setMetaDescriptionField(value) {
    const el = document.getElementById('aibui_meta_description_field');
    if (!el) return false;
    el.value = value || '';
    const evt = new Event('input', { bubbles: true });
    el.dispatchEvent(evt);
    return true;
  }

  // Update post title locally in the editor
  function updatePostTitle(title) {
    if (!title) return false;
    try {
      // Update the title in WordPress editor state
      wp.data.dispatch('core/editor').editPost({ title: title });
      return true;
    } catch (err) {
      console.error('Error updating post title:', err);
      return false;
    }
  }

  // Mark page as created via AI
  async function markPageAsAICreated() {
    try {
      const postId =
        wp?.data?.select("core/editor")?.getCurrentPostId?.() ||
        new URL(window.location.href, window.location.origin).searchParams.get("post") ||
        new URL(window.location.href, window.location.origin).searchParams.get("postId") ||
        new URL(window.location.href, window.location.origin).searchParams.get("p");

      if (!postId) {
        console.log("No post ID found to mark as AI-created");
        return;
      }

      const formData = new FormData();
      formData.append("action", "aibui_mark_ai_created");
      formData.append("post_id", postId);
      formData.append("nonce", aiBuilderVars.nonce);

      const response = await fetch(ajaxurl, {
        method: "POST",
        body: formData,
      });

      const result = await response.json();
      if (result.success) {
        console.log("Page marked as AI-created");
        // Injecter le CSS admin immédiatement après marquage
        injectAICreatedAdminCSS();
      } else {
        console.error("Failed to mark page as AI-created:", result);
      }
    } catch (error) {
      console.error("Error marking page as AI-created:", error);
    }
  }

  // Injecter le CSS admin pour masquer le titre
  function injectAICreatedAdminCSS() {
    // Vérifier si le style existe déjà
    if (document.getElementById("aibui-hide-ai-title-admin")) {
      return;
    }

    const style = document.createElement("style");
    style.id = "aibui-hide-ai-title-admin";
    style.type = "text/css";
    style.textContent = `
      /* Masquer le titre dans l'éditeur Gutenberg */
      .editor-post-title,
      .editor-post-title__input,
      .edit-post-visual-editor__post-title-wrapper,
      .editor-post-title__block,
      .wp-block[data-type="core/post-title"],
      .block-editor-block-list__block[data-type="core/post-title"],
      .wp-block-post-title.editor-post-title__block {
        display: none !important;
        visibility: hidden !important;
        height: 0 !important;
        margin: 0 !important;
        padding: 0 !important;
        overflow: hidden !important;
        opacity: 0 !important;
      }
      
      .edit-post-visual-editor__post-title-wrapper {
        display: none !important;
        visibility: hidden !important;
        height: 0 !important;
        margin: 0 !important;
        padding: 0 !important;
        overflow: hidden !important;
      }
    `;
    document.head.appendChild(style);
  }

  // Save meta description in post meta via WordPress AJAX
  async function saveMetaDescriptionInPostMeta(metaDesc) {
    try {
      const postId = wp.data.select("core/editor").getCurrentPostId();
      const formData = new FormData();
      formData.append("action", "aibui_save_meta_description");
      formData.append("nonce", window.aiBuilderNonce);
      formData.append("post_id", postId);
      formData.append("meta_desc", metaDesc || "");
      await fetch(window.ajaxurl, { method: "POST", body: formData });
    } catch (err) {
      console.error("Error saving meta description:", err);
    }
  }

  // Attendre que l'ID du post soit disponible (l'éditeur met un peu de temps à charger)
  async function waitForCurrentPostId(maxAttempts = 50, intervalMs = 100) {
    for (let attemptIndex = 0; attemptIndex < maxAttempts; attemptIndex++) {
      try {
        const postId = wp?.data?.select("core/editor")?.getCurrentPostId?.();
        if (postId) return postId;
      } catch (e) { }
      await new Promise((resolve) => setTimeout(resolve, intervalMs));
    }
    return null;
  }

  // Fonction pour charger le CSS depuis les meta du post via AJAX WordPress
  async function loadCSSFromPostMeta() {
    try {
      const postId = wp.data.select("core/editor").getCurrentPostId();

      const formData = new FormData();
      formData.append("action", "aibui_get_post_css");
      formData.append("nonce", window.aiBuilderNonce);
      formData.append("post_id", postId);

      const res = await fetch(window.ajaxurl, {
        method: "POST",
        body: formData,
      });

      if (res.ok) {
        const data = await res.json();
        if (data.success && data.data) {
          // Stocker les CSS séparément
          window.aiBuilderPageCSS = data.data.pageCss || "";
          window.aiBuilderBlockCSS = data.data.blockCss || "";

          // Utiliser le CSS combiné pour l'affichage
          const combinedCSS = data.data.combinedCss || "";
          currentCSSContent = combinedCSS;

          // Injecter le CSS dans l'éditeur et le frontend
          injectCSSInEditor(combinedCSS);
          injectCSSInFrontend(combinedCSS);

          // Afficher le bouton CSS s'il y a du CSS
          if (combinedCSS.trim()) {
            cssEditButton.style.display = "block";
          }

          console.log(
            "CSS loaded - Page:",
            window.aiBuilderPageCSS.length,
            "chars, Blocks:",
            window.aiBuilderBlockCSS.length,
            "chars"
          );
        }
      }
    } catch (err) {
      console.error("Error loading CSS from post meta:", err);
    }
  }



  // Initialiser le chargement du CSS une fois que l'ID du post est prêt
  (async function initEditorCssLoad() {
    // Vérifier que les variables AJAX sont disponibles
    if (!window.ajaxurl || !window.aiBuilderNonce) {
      console.warn("AI Builder: AJAX variables not available, skipping CSS load");
      return;
    }

    // Attendre un peu pour que WordPress soit complètement chargé
    await new Promise(resolve => setTimeout(resolve, 1000));

    const postId = await waitForCurrentPostId();
    if (postId) {
      loadCSSFromPostMeta();
    } else {
      console.warn(
        "AI Builder: unable to resolve current post ID to load CSS."
      );
    }
  })();

  function getPatternName() {
    try {
      const isPatternEditor = (typeof aiBuilderVars !== 'undefined' && !!aiBuilderVars.isPatternEditor) || window.location.pathname.includes('site-editor.php');
      if (!isPatternEditor) return '';
      const url = new URL(window.location.href);
      const p = url.searchParams.get('p') || url.searchParams.get('postId');
      if (!p) return '';
      const decoded = decodeURIComponent(p);
      // Use correct WordPress terminology: template part (header/footer)
      return decoded;
    } catch (e) {
      return '';
    }
  }

  async function sendMessageAIV3() {
    const messages = document.getElementById("chat-messages");
    const input = document.querySelector("#chat-input textarea");
    // const undoBtn = document.getElementById("chat-undo");
    const sendBtn = document.querySelector("#chat-input button");
    const toggleBtn = document.getElementById("chat-toggle");
    const question = input.value.trim();

    // Vérifier qu'un prompt est présent
    let finalQuestion = question;
    const patternName = getPatternName();
    if (!finalQuestion) {
      addMessage("Please enter a prompt first.", "assistant");
      return;
    }

    const conversationHistory = getConversationHistoryString(6);

    // 🔒 Désactiver le bouton + animation loading
    sendBtn.disabled = true;
    sendBtn.classList.add("loading");
    sendBtn.textContent = "Generating...";

    // 🎨 Activer l'effet visuel sur le toggle
    if (toggleBtn) {
      toggleBtn.classList.add("generating");
    }

    // Ajouter le message utilisateur à l'historique
    addMessage(finalQuestion, "user");
    input.value = "";
    messages.scrollTop = messages.scrollHeight;

    // Sauvegarder les blocs actuels
    previousBlocks = wp.data.select("core/block-editor").getBlocks();

    try {
      // Récupérer le token JWT
      const jwtToken = await getJwtToken();

      console.log('window.config: ', window.config);

      let res
      if (patternName) {
        const payload = {
          userPrompt: finalQuestion,
          // pageContent: pageContent,
          patternName: patternName,
          conversationHistory,
        };
        res = await fetch(
          window.config.apiUrl + "/ai-transform-page/generate-pattern",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              Authorization: `Bearer ${jwtToken}`,
            },
            body: JSON.stringify(payload),
          }
        );
      } else {
        const payload = {
          userPrompt: finalQuestion,
          // pageContent: pageContent,
          conversationHistory,
        };
        res = await fetch(
          window.config.apiUrl + "/ai-transform-page/v2-page-generation",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              Authorization: `Bearer ${jwtToken}`,
            },
            body: JSON.stringify(payload),
          }
        );
      }

      const data = await res.json();
      console.log("data: ", data);
      console.log(data.pageContent);
      if (data.error === "not-enough-credits") {
        showNotEnoughCreditsToast();
        return;
      }

      if (data.pageContent) {
        if (data.pageContent !== "[-no-content-to-return-]") {
          // Convertir chaque bloc JSON en bloc WordPress
          const newBlocks = data.pageContent.map(buildBlock);

          // 🔄 Supprimer tous les blocs existants
          wp.data.dispatch("core/block-editor").resetBlocks([]);
          // ➕ Insérer les nouveaux blocs
          wp.data.dispatch("core/block-editor").insertBlocks(newBlocks);
        }

        // Handle CSS if present
        if (data.cssContent && data.cssContent !== "[-no-content-to-return-]") {
          console.log("Injecting CSS...");

          // Sauvegarder le CSS dans les meta du post (type 'page')
          await saveCSSInPostMeta(data.cssContent, "page");

          // Recharger le CSS combiné depuis le serveur
          await loadCSSFromPostMeta();

          // Afficher le bouton CSS
          cssEditButton.style.display = "block";
        }

        // Handle meta description if present
        if (data.postMetaDesc && data.postMetaDesc !== "[-no-content-to-return-]") {
          // await saveMetaDescriptionInPostMeta(data.postMetaDesc);
          await setMetaDescriptionField(data.postMetaDesc);
        }

        // Handle title if present
        if (data.postTitle && data.postTitle !== "[-no-content-to-return-]") {
          updatePostTitle(data.postTitle);
        }

        let aiResponse =
          "Page content has been updated with AI-generated content.";
        if (data.aiResponse) {
          aiResponse = data.aiResponse;
        }

        addMessage(aiResponse, "assistant");

        // Marquer la page comme créée via IA
        await markPageAsAICreated();

        // undoBtn.style.display = "block"; // Affiche le bouton "Annuler"
        // Mettre à jour les crédits si la réponse contient creditsLeft
        if (typeof data.creditsLeft !== "undefined") {
          updateCreditsDisplay(data.creditsLeft);
        }
      } else {
        addMessage("Empty or invalid response.", "assistant");
      }
    } catch (err) {
      addMessage("Internal server error", "assistant");
      console.log("err : ", err);
    } finally {
      // 🔓 Réactiver le bouton + retirer animation
      sendBtn.disabled = false;
      sendBtn.classList.remove("loading");
      sendBtn.textContent = "Generate";
      messages.scrollTop = messages.scrollHeight;

      // 🎨 Désactiver l'effet visuel sur le toggle
      if (toggleBtn) {
        toggleBtn.classList.remove("generating");
      }
    }
  }

  sendBtn.onclick = sendMessageAIV3;
  input.addEventListener("keypress", (e) => {
    if (e.key === "Enter") sendMessageAIV3();
  });
  loadUserCredits();
  loadCSSFromPostMeta();
});

// Ajouter les styles CSS pour le bouton CSS et la modale
const cssStyles = document.createElement("style");
cssStyles.textContent = `
  /* Header du chat avec bouton CSS intégré */
  #chat-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 10px;
  }

  #chat-header-left {
    flex: 1;
  }

  

  #chat-header-left h3 {
    margin: 0 0 5px 0;
  }

  #chat-header-left p {
    margin: 0;
    font-size: 12px;
  }

  /* Bouton CSS intégré dans le header */
  #css-edit-button {
    width: 32px;
    height: 32px;
    background: #007cba;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transition: all 0.3s ease;
    padding: 0;
  }

  #css-edit-button:hover {
    background: #005a87;
    transform: scale(1.05);
  }

  #css-edit-button svg {
    width: 16px;
    height: 16px;
  }

  #css-edit-button img {
    width: 16px;
    height: 16px;
    display: block;
    margin: 0 auto;
  }

  /* Modale CSS */
  #css-modal {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.7);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 10000;
  }

  #css-modal-content {
    background: white;
    border-radius: 8px;
    width: 80%;
    max-width: 800px;
    max-height: 80%;
    display: flex;
    flex-direction: column;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
  }

  #css-modal-header {
    padding: 20px;
    border-bottom: 1px solid #ddd;
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  #css-modal-header h3 {
    margin: 0;
    color: #333;
  }

  /* Onglets CSS */
  #css-modal-tabs {
    display: flex;
    border-bottom: 1px solid #ddd;
    background: #f8f9fa;
  }

  .css-tab {
    flex: 1;
    padding: 12px 20px;
    border: none;
    background: transparent;
    cursor: pointer;
    font-size: 14px;
    font-weight: 500;
    color: #666;
    border-bottom: 2px solid transparent;
    transition: all 0.3s ease;
  }

  .css-tab:hover {
    background: #e9ecef;
    color: #333;
  }

  .css-tab.active {
    color: #007cba;
    border-bottom-color: #007cba;
    background: white;
  }

  #css-modal-close {
    background: none;
    border: none;
    font-size: 24px;
    cursor: pointer;
    color: #666;
    padding: 0;
    width: 30px;
    height: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  #css-modal-close:hover {
    color: #000;
  }

  #css-modal-body {
    flex: 1;
    padding: 20px;
    overflow: hidden;
  }

  #css-editor {
    width: 100%;
    height: 400px;
    border: 1px solid #ddd;
    border-radius: 4px;
    padding: 15px;
    font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
    font-size: 14px;
    line-height: 1.5;
    resize: vertical;
    box-sizing: border-box;
  }

  #css-editor:focus {
    outline: none;
    border-color: #007cba;
    box-shadow: 0 0 0 2px rgba(0, 124, 186, 0.2);
  }

  #css-modal-footer {
    padding: 20px;
    border-top: 1px solid #ddd;
    display: flex;
    gap: 10px;
    justify-content: flex-end;
  }

  #css-save, #css-cancel {
    padding: 10px 20px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    font-weight: 500;
  }

  #css-save {
    background: #007cba;
    color: white;
  }

  #css-save:hover {
    background: #005a87;
  }

  #css-cancel {
    background: #f0f0f0;
    color: #333;
  }

  #css-cancel:hover {
    background: #e0e0e0;
  }

  
`;

document.head.appendChild(cssStyles);

```
