// 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", `
🤖 AI Builder

AI Page Builder

⚠️ The current page content will be replaced by AI-generated content

` ); 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 = `🤖 ${message}`; } else { messageDiv.innerHTML = `👤 ${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 = `
You need to have an account and be logged in to use AI features. Go to Account
`; 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 = `
You don't have enough credits. Go to credits page
`; 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);