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