File: /var/www/bharti-foundation.stgviitor.com/wp-content/themes/lifeline/assets/js/assistant-float.js
/*
* Lifeline Theme – Floating AI Assistant
* --------------------------------------
* Entire file with formatting upgrades:
* • Bot answers now support **bold**, line breaks, and bullets.
* • User messages remain fully escaped.
* • History logic unchanged.
*/
jQuery(function ($) {
// Only run on admin pages
if (!window.pagenow || !$("body").hasClass("wp-admin")) {
return;
}
const STORAGE_KEY = "lifeline_ai_history_v2";
let isOpen = false;
let isMinimized = false;
/* ---------- Create floating widget HTML ---------- */
const createWidget = () => {
const widgetHTML = `
<div id="lifeline-float-widget" class="lifeline-float-widget">
<!-- Floating Button -->
<div id="lifeline-float-btn" class="lifeline-float-btn">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
<span class="lifeline-float-badge" id="lifeline-float-badge">?</span>
</div>
<!-- Chat Window -->
<div id="lifeline-float-window" class="lifeline-float-window">
<div class="lifeline-float-header">
<div class="lifeline-float-title">
<strong>Lifeline Assistant</strong>
<span class="lifeline-float-subtitle">Ask about theme features</span>
</div>
<div class="lifeline-float-actions">
<button id="lifeline-float-minimize" class="lifeline-float-action-btn" title="Minimize">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</button>
<button id="lifeline-float-close" class="lifeline-float-action-btn" title="Close">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</div>
<div class="lifeline-float-body">
<div id="lifeline-float-chat-box" class="lifeline-float-chat-box">
<div class="lifeline-welcome-msg">
<strong>Bot:</strong> Hi! I'm here to help you with the Lifeline theme. Ask me anything about features, customization, or settings.
</div>
</div>
<div class="lifeline-float-input-area">
<textarea
id="lifeline-float-input"
rows="2"
placeholder="Type your question about the theme..."
maxlength="1000"
></textarea>
<div class="lifeline-float-input-actions">
<button id="lifeline-float-clear" class="lifeline-float-clear-btn" title="Clear chat">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="3,6 5,6 21,6"></polyline>
<path d="M19,6v14a2,2 0,0 1,-2,2H7a2,2 0,0 1,-2,-2V6m3,0V4a2,2 0,0 1,2,-2h4a2,2 0,0 1,2,2v2"></path>
</svg>
</button>
<button id="lifeline-float-send" class="lifeline-float-send-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="22" y1="2" x2="11" y2="13"></line>
<polygon points="22,2 15,22 11,13 2,9 22,2"></polygon>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
`;
$("body").append(widgetHTML);
};
/* ---------- Helper functions ---------- */
const escHTML = (str) =>
str.replace(
/[&<>'"]/g,
(c) =>
({
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'",
}[c])
);
// ⭐ UPDATED: Markdown‑to‑HTML formatter for the bot’s replies
const formatAIText = (raw) =>
raw
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
.replace(/(^|\n)(\d+)\.\s+/g, "$1$2. ")
.replace(/\n/g, "<br>");
const scrollDown = () => {
const $chatBox = $("#lifeline-float-chat-box");
$chatBox.scrollTop($chatBox[0].scrollHeight);
};
// ⭐ UPDATED: allow HTML in Bot messages, keep user messages escaped
const append = (who, text) => {
const $chatBox = $("#lifeline-float-chat-box");
const cls = who === "You" ? "lifeline-msg-user" : "lifeline-msg-bot";
const raw = text ? String(text) : "No response received";
const content =
who === "Bot"
? formatAIText(raw) // Bot: markdown ➜ minimal HTML
: escHTML(raw); // User: fully escaped
$chatBox.append(
`<div class="lifeline-msg ${cls}"><strong>${who}:</strong> <span>${content}</span></div>`
);
scrollDown();
saveHistory();
};
/* ---------- History management ---------- */
const loadHistory = () => {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return;
let arr;
try {
arr = JSON.parse(raw);
} catch {
return;
}
// Clear welcome message if there's history
if (arr.length > 0) {
$("#lifeline-float-chat-box").find(".lifeline-welcome-msg").remove();
}
arr.forEach(({ who, text }) => append(who, text));
};
const saveHistory = () => {
const messages = [];
$("#lifeline-float-chat-box")
.children(".lifeline-msg")
.each(function () {
const who = $(this).hasClass("lifeline-msg-user") ? "You" : "Bot";
const fullText = $(this).text() || "";
const text = fullText.replace(/^You:\s|^Bot:\s/, "") || "";
if (text.trim()) {
messages.push({ who, text });
}
});
localStorage.setItem(STORAGE_KEY, JSON.stringify(messages));
};
const clearHistory = () => {
localStorage.removeItem(STORAGE_KEY);
$("#lifeline-float-chat-box")
.empty()
.append(
'<div class="lifeline-welcome-msg"><strong>Bot:</strong> Hi! I\'m here to help you with the Lifeline theme. Ask me anything about features, customization, or settings.</div>'
);
};
/* ---------- Typing indicator ---------- */
const setTyping = (on) => {
const $chatBox = $("#lifeline-float-chat-box");
if (on) {
$chatBox.append(
`<div class="lifeline-msg lifeline-msg-bot typing-holder">
<strong>Shad:</strong>
<span class="lifeline-typing-indicator">
<span></span><span></span><span></span>
</span>
</div>`
);
scrollDown();
} else {
$chatBox.find(".typing-holder").remove();
}
};
/* ---------- Send message ---------- */
const sendMessage = () => {
const $input = $("#lifeline-float-input");
const raw = $input.val().trim();
if (!raw) return;
// Remove welcome message on first user message
$("#lifeline-float-chat-box").find(".lifeline-welcome-msg").remove();
append("You", raw);
$input.val("").focus();
setTyping(true);
// Debug: Check if lifelineAI is available
if (typeof lifelineAI === "undefined") {
setTyping(false);
append(
"Bot",
"Configuration error: lifelineAI not found. Please check your setup."
);
return;
}
$.post(
lifelineAI.ajax_url,
{
action: "lifeline_send_to_ai",
nonce: lifelineAI.nonce,
message: raw,
},
(res) => {
setTyping(false);
// console.log("Response received:", res); // Debug log
if (res && res.success) {
const botResponse = res.data || "No response data received";
append("Bot", botResponse);
} else {
const errorMsg =
res && res.data ? res.data : "Unknown error occurred";
append("Bot", "Error: " + errorMsg);
}
}
).fail((xhr, status, error) => {
setTyping(false);
console.error("AJAX request failed:", xhr, status, error); // Debug log
append("Bot", "Network error: " + (error || "Request failed"));
});
};
/* ---------- Widget controls ---------- */
const openWidget = () => {
isOpen = true;
isMinimized = false;
$("#lifeline-float-window").addClass("open");
$("#lifeline-float-btn").addClass("open");
$("#lifeline-float-badge").hide();
$("#lifeline-float-input").focus();
};
const closeWidget = () => {
isOpen = false;
isMinimized = false;
$("#lifeline-float-window").removeClass("open minimized");
$("#lifeline-float-btn").removeClass("open");
$("#lifeline-float-badge").show();
};
const minimizeWidget = () => {
isMinimized = true;
$("#lifeline-float-window").addClass("minimized");
};
const restoreWidget = () => {
isMinimized = false;
$("#lifeline-float-window").removeClass("minimized");
$("#lifeline-float-input").focus();
};
/* ---------- Initialize ---------- */
const init = () => {
createWidget();
loadHistory();
// Event listeners
$("#lifeline-float-btn").on("click", () => {
if (isOpen) {
if (isMinimized) {
restoreWidget();
} else {
closeWidget();
}
} else {
openWidget();
}
});
$("#lifeline-float-close").on("click", closeWidget);
$("#lifeline-float-minimize").on("click", minimizeWidget);
$("#lifeline-float-clear").on("click", clearHistory);
$("#lifeline-float-send").on("click", sendMessage);
$("#lifeline-float-input").on("keydown", (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
// Auto-resize textarea
$("#lifeline-float-input").on("input", function () {
this.style.height = "auto";
this.style.height = Math.min(this.scrollHeight, 100) + "px";
});
// Close on escape
$(document).on("keydown", (e) => {
if (e.key === "Escape" && isOpen) {
closeWidget();
}
});
};
// Initialize when DOM is ready
init();
});