function scrollToBottom(animate) {
var messages = document.getElementById("messages");
if (messages) {
if (animate) {
messages.scrollTo({ top: messages.scrollHeight, behavior: "smooth" });
} else {
messages.scrollTop = messages.scrollHeight;
}
}
}
function updateScrollButton() {
var messages = document.getElementById("messages");
var scrollBtn = document.getElementById("scrollToBottom");
if (!messages || !scrollBtn) return;
var isNearBottom = messages.scrollHeight - messages.scrollTop - messages.clientHeight < 100;
if (isNearBottom) {
scrollBtn.classList.remove("visible");
} else {
scrollBtn.classList.add("visible");
}
}
function renderMentionInMessage(content) {
return content.replace(/@(\w+):([^\s]+)/g, function (match, type, name) {
var entityType = EntityTypes[type.toLowerCase()];
if (entityType) {
return '' +
'' + entityType.icon + "" +
'@' + type + ":" + escapeHtml(name) + "" +
"";
}
return match;
});
}
function stripThinkTags(content) {
// Remove ... and anything in between
return content.replace(/[\s\S]*?(?:<\/think>|$)/gi, "").trim();
}
function stripMarkdownBlocks(content) {
var cleanContent = stripThinkTags(content);
var htmlMatch = cleanContent.match(/^```(?:html|xml)?\s*\n([\s\S]+?)\n?```$/i);
if (htmlMatch) return htmlMatch[1].trim();
return cleanContent;
}
function addMessage(sender, content, msgId) {
var messages = document.getElementById("messages");
if (!messages) return;
var div = document.createElement("div");
div.className = "message " + sender;
if (msgId) div.id = msgId;
if (sender === "user") {
var processedContent = renderMentionInMessage(escapeHtml(content));
div.innerHTML = '' + processedContent + "
";
} else {
var cleanContent = stripMarkdownBlocks(content);
if (msgId) {
parsed = '...
';
} else if (hasHtmlTags) {
parsed = cleanContent; // Don't escape HTML tags
} else {
parsed = typeof marked !== "undefined" && marked.parse
? marked.parse(cleanContent)
: escapeHtml(cleanContent);
}
parsed = renderMentionInMessage(parsed);
div.innerHTML = '' + parsed + "
";
}
messages.appendChild(div);
if (!ChatState.isUserScrolling) {
scrollToBottom(true);
} else {
updateScrollButton();
}
setupMentionClickHandlers(div);
}
function isTagBalanced(html) {
if (!html) return true;
var lastChevronOpen = html.lastIndexOf('<');
var lastChevronClose = html.lastIndexOf('>');
if (lastChevronOpen > lastChevronClose) return false;
return true;
}
function updateStreaming(content) {
var el = document.getElementById(ChatState.streamingMessageId);
if (!el) return;
var msgContent = el.querySelector(".message-content");
var cleanContent = stripMarkdownBlocks(content);
var isHtml = /<\/?[a-zA-Z][^>]*>|/i.test(cleanContent);
if (isHtml) {
if (!el.querySelector(".streaming-loading")) {
var loader = document.createElement("div");
loader.className = "streaming-loading";
loader.innerHTML = '...';
msgContent.appendChild(loader);
}
} else {
var parsed = typeof marked !== "undefined" && marked.parse
? marked.parse(cleanContent)
: escapeHtml(cleanContent);
parsed = renderMentionInMessage(parsed);
msgContent.innerHTML = parsed;
if (!ChatState.isUserScrolling) scrollToBottom(true);
}
}
function finalizeStreaming() {
var el = document.getElementById(ChatState.streamingMessageId);
if (el) {
var cleanContent = stripMarkdownBlocks(ChatState.currentStreamingContent);
var hasHtmlTags = /<\/?[a-zA-Z][^>]*>|/i.test(cleanContent);
var parsed;
if (hasHtmlTags) {
parsed = cleanContent;
} else {
parsed = typeof marked !== "undefined" && marked.parse
? marked.parse(cleanContent)
: escapeHtml(cleanContent);
}
parsed = renderMentionInMessage(parsed);
el.querySelector(".message-content").innerHTML = parsed;
el.removeAttribute("id");
setupMentionClickHandlers(el);
if (!ChatState.isUserScrolling) scrollToBottom(true);
}
ChatState.streamingMessageId = null;
ChatState.currentStreamingContent = "";
ChatState.streamingBuffer = "";
}
function processMessage(data) {
if (data.is_complete) {
if (ChatState.isStreaming) {
finalizeStreaming();
} else if (data.content && data.content.trim() !== "") {
addMessage("bot", data.content);
}
ChatState.isStreaming = false;
if (data.suggestions && Array.isArray(data.suggestions) && data.suggestions.length > 0) {
renderSuggestions(data.suggestions);
}
if (data.switchers && Array.isArray(data.switchers) && data.switchers.length > 0) {
renderBotSwitchers(data.switchers);
}
} else {
if (!ChatState.isStreaming) {
ChatState.isStreaming = true;
ChatState.streamingMessageId = "streaming-" + Date.now();
ChatState.currentStreamingContent = data.content || "";
addMessage("bot", ChatState.currentStreamingContent, ChatState.streamingMessageId);
ChatState.lastRenderTime = Date.now();
} else {
ChatState.currentStreamingContent += data.content || "";
var now = Date.now();
if (now - ChatState.lastRenderTime > ChatState.renderInterval) {
updateStreaming(ChatState.currentStreamingContent);
}
}
}
}