botui/ui/suite/chat/chat.html

1151 lines
43 KiB
HTML
Raw Normal View History

<link rel="stylesheet" href="/suite/chat/chat.css" />
2026-02-13 22:31:49 +00:00
<link rel="stylesheet" href="/suite/css/markdown-message.css" />
<div class="chat-layout" id="chat-app">
<!-- Connection Status -->
<div class="connection-status connecting" id="connectionStatus" style="display: none;">
<span class="connection-status-dot"></span>
<span class="connection-text">Connecting...</span>
</div>
<main id="messages"></main>
<footer>
<div class="suggestions-container" id="suggestions"></div>
<div class="mention-dropdown" id="mentionDropdown">
<div class="mention-header">
<span class="mention-title" data-i18n="chat-mention-title">Reference Entity</span>
</div>
<div class="mention-results" id="mentionResults"></div>
</div>
<form class="input-container" id="chatForm">
<input name="content" id="messageInput" type="text" placeholder="Message... (type @ to mention)"
data-i18n-placeholder="chat-placeholder" autofocus autocomplete="off" />
<button type="submit" id="sendBtn" title="Send" data-i18n-title="chat-send">
</button>
</form>
</footer>
<button class="scroll-to-bottom" id="scrollToBottom" title="Scroll to bottom">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
</div>
<div class="entity-card-tooltip" id="entityCardTooltip">
<div class="entity-card-header">
<span class="entity-card-type"></span>
<span class="entity-card-status"></span>
</div>
<div class="entity-card-title"></div>
<div class="entity-card-details"></div>
<div class="entity-card-actions">
<button class="entity-card-btn" data-action="view" data-i18n="action-view">
View
</button>
</div>
</div>
<script>
(function () {
"use strict";
function notify(message, type) {
type = type || "info";
if (window.GBAlerts) {
if (type === "success") {
window.GBAlerts.info("Chat", message);
} else if (type === "error") {
window.GBAlerts.warning("Chat", message);
} else {
window.GBAlerts.info("Chat", message);
}
}
}
var WS_BASE_URL =
window.location.protocol === "https:" ? "wss://" : "ws://";
var WS_URL = WS_BASE_URL + window.location.host + "/ws/chat";
var MessageType = {
EXTERNAL: 0,
USER: 1,
BOT_RESPONSE: 2,
CONTINUE: 3,
SUGGESTION: 4,
CONTEXT_CHANGE: 5,
};
var EntityTypes = {
lead: { icon: "👤", color: "#4CAF50", label: "Lead", route: "crm" },
opportunity: {
icon: "💰",
color: "#FF9800",
label: "Opportunity",
route: "crm",
},
account: {
icon: "🏢",
color: "#2196F3",
label: "Account",
route: "crm",
},
contact: {
icon: "📇",
color: "#9C27B0",
label: "Contact",
route: "crm",
},
invoice: {
icon: "📄",
color: "#F44336",
label: "Invoice",
route: "billing",
},
quote: {
icon: "📋",
color: "#607D8B",
label: "Quote",
route: "billing",
},
case: {
icon: "🎫",
color: "#E91E63",
label: "Case",
route: "tickets",
},
product: {
icon: "📦",
color: "#795548",
label: "Product",
route: "products",
},
service: {
icon: "⚙️",
color: "#00BCD4",
label: "Service",
route: "products",
},
};
var ws = null;
var currentSessionId = null;
var currentUserId = null;
var currentBotId = "default";
var currentBotName = "default";
var isStreaming = false;
var streamingMessageId = null;
var currentStreamingContent = "";
var reconnectAttempts = 0;
var maxReconnectAttempts = 5;
var disconnectNotified = false;
var isUserScrolling = false;
var mentionState = {
active: false,
query: "",
startPos: -1,
selectedIndex: 0,
results: [],
};
function escapeHtml(text) {
var div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
// Scroll handling
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");
}
}
// Scroll-to-bottom button click
var scrollBtn = document.getElementById("scrollToBottom");
if (scrollBtn) {
scrollBtn.addEventListener("click", function () {
scrollToBottom(true);
isUserScrolling = false;
});
}
// Detect user scrolling
var messagesEl = document.getElementById("messages");
if (messagesEl) {
messagesEl.addEventListener("scroll", function () {
isUserScrolling = true;
updateScrollButton();
// Reset isUserScrolling after 2 seconds of no scrolling
clearTimeout(messagesEl.scrollTimeout);
messagesEl.scrollTimeout = setTimeout(function () {
isUserScrolling = false;
}, 2000);
});
}
function renderMentionInMessage(content) {
return content.replace(
/@(\w+):([^\s]+)/g,
function (match, type, name) {
var entityType = EntityTypes[type.toLowerCase()];
if (entityType) {
return (
'<span class="mention-tag" data-type="' +
type +
'" data-name="' +
escapeHtml(name) +
'">' +
'<span class="mention-icon">' +
entityType.icon +
"</span>" +
'<span class="mention-text">@' +
type +
":" +
escapeHtml(name) +
"</span>" +
"</span>"
);
}
return match;
},
);
}
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 =
'<div class="message-content user-message">' +
processedContent +
"</div>";
} else {
var parsed =
typeof marked !== "undefined" && marked.parse
? marked.parse(content)
: escapeHtml(content);
parsed = renderMentionInMessage(parsed);
div.innerHTML =
'<div class="message-content bot-message">' +
parsed +
"</div>";
}
messages.appendChild(div);
// Auto-scroll to bottom unless user is manually scrolling
if (!isUserScrolling) {
scrollToBottom(true);
} else {
updateScrollButton();
}
setupMentionClickHandlers(div);
}
function setupMentionClickHandlers(container) {
var mentions = container.querySelectorAll(".mention-tag");
mentions.forEach(function (mention) {
mention.addEventListener("click", function (e) {
e.preventDefault();
var type = this.getAttribute("data-type");
var name = this.getAttribute("data-name");
navigateToEntity(type, name);
});
mention.addEventListener("mouseenter", function (e) {
var type = this.getAttribute("data-type");
var name = this.getAttribute("data-name");
showEntityCard(type, name, e.target);
});
mention.addEventListener("mouseleave", function () {
hideEntityCard();
});
});
}
function navigateToEntity(type, name) {
var entityType = EntityTypes[type.toLowerCase()];
if (entityType) {
var route = entityType.route;
window.location.hash = "#" + route;
var htmxLink = document.querySelector(
'a[data-section="' + route + '"]',
);
if (htmxLink) {
htmx.trigger(htmxLink, "click");
}
}
}
function showEntityCard(type, name, targetEl) {
var card = document.getElementById("entityCardTooltip");
var entityType = EntityTypes[type.toLowerCase()];
if (!card || !entityType) return;
card.querySelector(".entity-card-type").textContent =
entityType.label;
card.querySelector(".entity-card-type").style.background =
entityType.color;
card.querySelector(".entity-card-title").textContent =
entityType.icon + " " + name;
card.querySelector(".entity-card-status").textContent = "";
card.querySelector(".entity-card-details").textContent =
"Loading...";
var rect = targetEl.getBoundingClientRect();
card.style.left = rect.left + "px";
card.style.top = rect.top - card.offsetHeight - 8 + "px";
card.classList.add("visible");
fetchEntityDetails(type, name).then(function (details) {
if (card.classList.contains("visible")) {
card.querySelector(".entity-card-details").innerHTML =
details;
}
});
}
function hideEntityCard() {
var card = document.getElementById("entityCardTooltip");
if (card) {
card.classList.remove("visible");
}
}
function fetchEntityDetails(type, name) {
return fetch(
"/api/search/entity?type=" +
encodeURIComponent(type) +
"&name=" +
encodeURIComponent(name),
)
.then(function (r) {
return r.json();
})
.then(function (data) {
if (data && data.details) {
return data.details;
}
return "No additional details available";
})
.catch(function () {
return "Unable to load details";
});
}
function showMentionDropdown() {
var dropdown = document.getElementById("mentionDropdown");
if (dropdown) {
dropdown.classList.add("visible");
}
}
function hideMentionDropdown() {
var dropdown = document.getElementById("mentionDropdown");
if (dropdown) {
dropdown.classList.remove("visible");
}
mentionState.active = false;
mentionState.query = "";
mentionState.startPos = -1;
mentionState.selectedIndex = 0;
mentionState.results = [];
}
function searchEntities(query) {
if (!query || query.length < 1) {
var defaultResults = Object.keys(EntityTypes).map(
function (type) {
return {
type: type,
name: EntityTypes[type].label,
icon: EntityTypes[type].icon,
isTypeHint: true,
};
},
);
renderMentionResults(defaultResults);
return;
}
var colonIndex = query.indexOf(":");
if (colonIndex > 0) {
var entityType = query.substring(0, colonIndex).toLowerCase();
var searchTerm = query.substring(colonIndex + 1);
if (EntityTypes[entityType]) {
fetchEntitiesOfType(entityType, searchTerm);
return;
}
}
var filteredTypes = Object.keys(EntityTypes)
.filter(function (type) {
return (
type.toLowerCase().indexOf(query.toLowerCase()) === 0 ||
EntityTypes[type].label
.toLowerCase()
.indexOf(query.toLowerCase()) === 0
);
})
.map(function (type) {
return {
type: type,
name: EntityTypes[type].label,
icon: EntityTypes[type].icon,
isTypeHint: true,
};
});
renderMentionResults(filteredTypes);
}
function fetchEntitiesOfType(type, searchTerm) {
fetch(
"/api/search/entities?type=" +
encodeURIComponent(type) +
"&q=" +
encodeURIComponent(searchTerm || ""),
)
.then(function (r) {
return r.json();
})
.then(function (data) {
var results = (data.results || []).map(function (item) {
return {
type: type,
name: item.name || item.title || item.number,
id: item.id,
icon: EntityTypes[type].icon,
subtitle: item.subtitle || item.status || "",
isTypeHint: false,
};
});
if (results.length === 0) {
results = [
{
type: type,
name: "No results for '" + searchTerm + "'",
icon: "❌",
isTypeHint: false,
disabled: true,
},
];
}
renderMentionResults(results);
})
.catch(function () {
renderMentionResults([
{
type: type,
name: "Search unavailable",
icon: "⚠️",
isTypeHint: false,
disabled: true,
},
]);
});
}
function renderMentionResults(results) {
var container = document.getElementById("mentionResults");
if (!container) return;
mentionState.results = results;
mentionState.selectedIndex = 0;
container.innerHTML = results
.map(function (item, index) {
var classes = "mention-item";
if (index === mentionState.selectedIndex)
classes += " selected";
if (item.disabled) classes += " disabled";
var subtitle = item.subtitle
? '<span class="mention-item-subtitle">' +
escapeHtml(item.subtitle) +
"</span>"
: "";
var hint = item.isTypeHint
? '<span class="mention-item-hint">Type : to search</span>'
: "";
return (
'<div class="' +
classes +
'" data-index="' +
index +
'" data-type="' +
item.type +
'" data-name="' +
escapeHtml(item.name) +
'" data-is-type="' +
item.isTypeHint +
'">' +
'<span class="mention-item-icon">' +
item.icon +
"</span>" +
'<span class="mention-item-content">' +
'<span class="mention-item-name">' +
escapeHtml(item.name) +
"</span>" +
subtitle +
hint +
"</span>" +
"</div>"
);
})
.join("");
container
.querySelectorAll(".mention-item:not(.disabled)")
.forEach(function (item) {
item.addEventListener("click", function () {
selectMentionItem(
parseInt(this.getAttribute("data-index")),
);
});
});
}
function selectMentionItem(index) {
var item = mentionState.results[index];
if (!item || item.disabled) return;
var input = document.getElementById("messageInput");
if (!input) return;
var value = input.value;
var beforeMention = value.substring(0, mentionState.startPos);
var afterMention = value.substring(input.selectionStart);
var insertText;
if (item.isTypeHint) {
insertText = "@" + item.type + ":";
mentionState.query = item.type + ":";
mentionState.startPos = beforeMention.length;
input.value = beforeMention + insertText + afterMention;
input.setSelectionRange(
beforeMention.length + insertText.length,
beforeMention.length + insertText.length,
);
searchEntities(mentionState.query);
return;
} else {
insertText = "@" + item.type + ":" + item.name + " ";
input.value = beforeMention + insertText + afterMention;
input.setSelectionRange(
beforeMention.length + insertText.length,
beforeMention.length + insertText.length,
);
hideMentionDropdown();
}
input.focus();
}
function updateMentionSelection(direction) {
var enabledResults = mentionState.results.filter(function (r) {
return !r.disabled;
});
if (enabledResults.length === 0) return;
var currentEnabled = 0;
for (var i = 0; i < mentionState.selectedIndex; i++) {
if (!mentionState.results[i].disabled) currentEnabled++;
}
currentEnabled += direction;
if (currentEnabled < 0) currentEnabled = enabledResults.length - 1;
if (currentEnabled >= enabledResults.length) currentEnabled = 0;
var newIndex = 0;
var count = 0;
for (var j = 0; j < mentionState.results.length; j++) {
if (!mentionState.results[j].disabled) {
if (count === currentEnabled) {
newIndex = j;
break;
}
count++;
}
}
mentionState.selectedIndex = newIndex;
var items = document.querySelectorAll(
"#mentionResults .mention-item",
);
items.forEach(function (item, idx) {
item.classList.toggle("selected", idx === newIndex);
});
var selectedItem = document.querySelector(
"#mentionResults .mention-item.selected",
);
if (selectedItem) {
selectedItem.scrollIntoView({ block: "nearest" });
}
}
function handleMentionInput(e) {
var input = e.target;
var value = input.value;
var cursorPos = input.selectionStart;
var textBeforeCursor = value.substring(0, cursorPos);
var atIndex = textBeforeCursor.lastIndexOf("@");
if (atIndex >= 0) {
var charBeforeAt =
atIndex > 0 ? textBeforeCursor[atIndex - 1] : " ";
if (charBeforeAt === " " || atIndex === 0) {
var query = textBeforeCursor.substring(atIndex + 1);
if (!query.includes(" ")) {
mentionState.active = true;
mentionState.startPos = atIndex;
mentionState.query = query;
showMentionDropdown();
searchEntities(query);
return;
}
}
}
if (mentionState.active) {
hideMentionDropdown();
}
}
function handleMentionKeydown(e) {
if (!mentionState.active) return false;
if (e.key === "ArrowDown") {
e.preventDefault();
updateMentionSelection(1);
return true;
}
if (e.key === "ArrowUp") {
e.preventDefault();
updateMentionSelection(-1);
return true;
}
if (e.key === "Enter" || e.key === "Tab") {
e.preventDefault();
selectMentionItem(mentionState.selectedIndex);
return true;
}
if (e.key === "Escape") {
e.preventDefault();
hideMentionDropdown();
return true;
}
return false;
}
function updateStreaming(content) {
var el = document.getElementById(streamingMessageId);
if (el) {
var parsed =
typeof marked !== "undefined" && marked.parse
? marked.parse(content)
: escapeHtml(content);
parsed = renderMentionInMessage(parsed);
el.querySelector(".message-content").innerHTML = parsed;
}
}
function finalizeStreaming() {
var el = document.getElementById(streamingMessageId);
if (el) {
var parsed =
typeof marked !== "undefined" && marked.parse
? marked.parse(currentStreamingContent)
: escapeHtml(currentStreamingContent);
parsed = renderMentionInMessage(parsed);
el.querySelector(".message-content").innerHTML = parsed;
el.removeAttribute("id");
setupMentionClickHandlers(el);
}
streamingMessageId = null;
currentStreamingContent = "";
}
function processMessage(data) {
if (data.is_complete) {
if (isStreaming) {
finalizeStreaming();
} else {
if (data.content && data.content.trim() !== "") {
addMessage("bot", data.content);
}
}
isStreaming = false;
// Render suggestions when message is complete
if (data.suggestions && Array.isArray(data.suggestions) && data.suggestions.length > 0) {
renderSuggestions(data.suggestions);
}
} else {
if (!isStreaming) {
isStreaming = true;
streamingMessageId = "streaming-" + Date.now();
currentStreamingContent = data.content || "";
addMessage(
"bot",
currentStreamingContent,
streamingMessageId,
);
} else {
currentStreamingContent += data.content || "";
updateStreaming(currentStreamingContent);
}
}
}
// Render suggestion buttons
function renderSuggestions(suggestions) {
var suggestionsEl = document.getElementById("suggestions");
if (!suggestionsEl) {
console.warn("Suggestions container not found");
return;
}
// Clear existing suggestions
suggestionsEl.innerHTML = "";
console.log("Rendering " + suggestions.length + " suggestions");
suggestions.forEach(function (suggestion) {
var chip = document.createElement("button");
chip.className = "suggestion-chip";
chip.textContent = suggestion.text || "Suggestion";
// Use window.sendMessage which is already exposed
chip.onclick = (function (sugg) {
return function () {
console.log("Suggestion clicked:", sugg);
// Check if there's an action to parse
if (sugg.action) {
try {
var action = typeof sugg.action === "string"
? JSON.parse(sugg.action)
: sugg.action;
console.log("Parsed action:", action);
if (action.type === "invoke_tool") {
// Send the display text so it shows correctly in chat
// The backend will recognize this as a tool request
window.sendMessage(sugg.text);
} else if (action.type === "send_message") {
window.sendMessage(action.message || sugg.text);
} else if (action.type === "select_context") {
window.sendMessage(action.context);
} else {
window.sendMessage(sugg.text);
}
} catch (e) {
console.error("Failed to parse action:", e, "falling back to text");
window.sendMessage(sugg.text);
}
} else {
// No action, just send the text
window.sendMessage(sugg.text);
}
};
})(suggestion);
suggestionsEl.appendChild(chip);
});
}
function sendMessage(messageContent) {
var input = document.getElementById("messageInput");
if (!input) {
console.error("Chat input not found");
return;
}
// If no messageContent provided, read from input
var content = messageContent || input.value.trim();
if (!content) {
return;
}
// If called from input field (no messageContent provided), clear input
if (!messageContent) {
hideMentionDropdown();
input.value = "";
input.focus();
}
addMessage("user", content);
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(
JSON.stringify({
bot_id: currentBotId,
user_id: currentUserId,
session_id: currentSessionId,
channel: "web",
content: content,
message_type: MessageType.USER,
timestamp: new Date().toISOString(),
}),
);
} else {
notify("Not connected to server. Message not sent.", "warning");
}
}
window.sendMessage = sendMessage;
// Expose session info for suggestion clicks
window.getChatSessionInfo = function () {
return {
ws: ws,
currentBotId: currentBotId,
currentUserId: currentUserId,
currentSessionId: currentSessionId,
currentBotName: currentBotName
};
};
function connectWebSocket() {
if (ws) {
ws.close();
}
updateConnectionStatus("connecting");
var url =
WS_URL +
"?session_id=" +
currentSessionId +
"&user_id=" +
currentUserId +
"&bot_name=" +
currentBotName;
ws = new WebSocket(url);
ws.onopen = function () {
console.log("WebSocket connected");
reconnectAttempts = 0;
disconnectNotified = false;
updateConnectionStatus("connected");
};
ws.onmessage = function (event) {
try {
var data = JSON.parse(event.data);
console.log("Chat WebSocket received:", data);
// Ignore connection confirmation
if (data.type === "connected") return;
// Process system events (theme changes, etc)
if (data.event) {
if (data.event === "change_theme") {
applyThemeData(data.data || {});
}
return;
}
// Check if content contains theme change events (JSON strings)
if (data.content && typeof data.content === "string") {
try {
var contentObj = JSON.parse(data.content);
if (contentObj.event === "change_theme") {
applyThemeData(contentObj.data || {});
return;
}
} catch (e) {
// Content is not JSON, continue processing
}
}
// Only process bot responses
if (data.message_type === MessageType.BOT_RESPONSE) {
console.log("Processing bot response:", data);
processMessage(data);
} else {
console.log("Ignoring non-bot message:", data);
}
} catch (e) {
console.error("WS message error:", e);
}
};
ws.onclose = function () {
updateConnectionStatus("disconnected");
if (!disconnectNotified) {
notify("Disconnected from chat server", "error");
disconnectNotified = true;
}
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
updateConnectionStatus("connecting");
setTimeout(connectWebSocket, 1000 * reconnectAttempts);
}
};
ws.onerror = function (e) {
console.error("WebSocket error:", e);
updateConnectionStatus("disconnected");
};
}
// Apply theme data from WebSocket events
function applyThemeData(themeData) {
console.log("Applying theme data:", themeData);
2026-02-14 10:13:40 +00:00
var color1 = themeData.color1 || themeData.data?.color1 || "black";
var color2 = themeData.color2 || themeData.data?.color2 || "white";
var logo = themeData.logo_url || themeData.data?.logo_url || "";
var title = themeData.title || themeData.data?.title || window.__INITIAL_BOT_NAME__ || "Chat";
// Set CSS variables for colors on document element
document.documentElement.style.setProperty("--chat-color1", color1);
document.documentElement.style.setProperty("--chat-color2", color2);
document.documentElement.style.setProperty("--suggestion-color", color1);
document.documentElement.style.setProperty("--suggestion-bg", color2);
// Also set on root for better cascading
document.documentElement.style.setProperty("--color1", color1);
document.documentElement.style.setProperty("--color2", color2);
// Update suggestion button colors to match theme
document.documentElement.style.setProperty("--primary", color1);
document.documentElement.style.setProperty("--accent", color1);
console.log("Theme applied:", { color1: color1, color2: color2, logo: logo, title: title });
}
// Load bot config and apply colors/logo
function loadBotConfig() {
var botName = window.__INITIAL_BOT_NAME__ || "default";
fetch("/api/bot/config?bot_name=" + encodeURIComponent(botName))
.then(function (response) {
return response.json();
})
.then(function (config) {
if (!config) return;
// Get the theme manager's theme for this bot to check if user selected a different theme
var botId = botName.toLowerCase();
var botTheme = window.ThemeManager ? (
// Get bot-specific theme from theme manager's mapping
(window.ThemeManager.getAvailableThemes &&
window.ThemeManager.getAvailableThemes().find(t => t.id === botId)) ||
// Fallback to localStorage
localStorage.getItem("gb-theme")
) : localStorage.getItem("gb-theme");
// Check if bot config has a theme-base setting
var configThemeBase = config.theme_base || config["theme-base"] || "light";
// Only use bot config colors if:
// 1. No theme has been explicitly selected by user (localStorage empty or default)
// 2. AND the bot config's theme-base matches the current theme
var localStorageTheme = localStorage.getItem("gb-theme");
var useBotConfigColors = !localStorageTheme ||
localStorageTheme === "default" ||
localStorageTheme === configThemeBase;
2026-02-14 10:13:40 +00:00
// Apply colors from config (API returns snake_case)
var color1 = config.theme_color1 || config["theme-color1"] || config["Theme Color"] || "#3b82f6";
var color2 = config.theme_color2 || config["theme-color2"] || "#f5deb3";
var title = config.theme_title || config["theme-title"] || botName;
var logo = config.theme_logo || config["theme-logo"] || "";
// Only set bot config colors if user hasn't selected a different theme
if (useBotConfigColors) {
document.documentElement.style.setProperty("--chat-color1", color1);
document.documentElement.style.setProperty("--chat-color2", color2);
document.documentElement.style.setProperty("--suggestion-color", color1);
document.documentElement.style.setProperty("--suggestion-bg", color2);
document.documentElement.style.setProperty("--color1", color1);
document.documentElement.style.setProperty("--color2", color2);
document.documentElement.style.setProperty("--primary", color1);
document.documentElement.style.setProperty("--accent", color1);
console.log("Bot config colors applied:", { color1: color1, color2: color2 });
} else {
console.log("Bot config colors skipped - user selected custom theme:", localStorageTheme);
}
// Update logo if provided
if (logo) {
var logoImg = document.querySelector(".logo-icon-img");
if (logoImg) {
logoImg.src = logo;
logoImg.alt = title || botName;
logoImg.style.display = "block";
}
// Hide the SVG logo when image logo is used
var logoSvg = document.querySelector(".logo-icon-svg");
if (logoSvg) {
logoSvg.style.display = "none";
}
}
console.log("Bot config loaded:", { color1: color1, color2: color2, title: title, logo: logo });
})
.catch(function (e) {
console.log("Could not load bot config:", e);
});
}
function initChat() {
// Load bot config first
loadBotConfig();
// Just proceed with chat initialization - no auth check
proceedWithChatInit();
}
function proceedWithChatInit() {
var botName = window.__INITIAL_BOT_NAME__ || "default";
fetch("/api/auth?bot_name=" + encodeURIComponent(botName))
.then(function (response) {
return response.json();
})
.then(function (auth) {
currentUserId = auth.user_id;
currentSessionId = auth.session_id;
currentBotId = auth.bot_id || "default";
currentBotName = botName;
console.log("Auth:", {
currentUserId: currentUserId,
currentSessionId: currentSessionId,
currentBotId: currentBotId,
currentBotName: currentBotName,
});
connectWebSocket();
})
.catch(function (e) {
console.error("Auth failed:", e);
notify("Failed to connect to chat server", "error");
setTimeout(proceedWithChatInit, 3000);
});
}
function updateConnectionStatus(status) {
var statusEl = document.getElementById("connectionStatus");
if (!statusEl) return;
statusEl.className = "connection-status " + status;
var statusText = statusEl.querySelector(".connection-text");
if (statusText) {
switch (status) {
case "connected":
statusText.textContent = "Connected";
statusEl.style.display = "none";
break;
case "disconnected":
statusText.textContent = "Disconnected";
statusEl.style.display = "flex";
break;
case "connecting":
statusText.textContent = "Connecting...";
statusEl.style.display = "flex";
break;
}
}
}
function setupEventHandlers() {
var form = document.getElementById("chatForm");
var input = document.getElementById("messageInput");
var sendBtn = document.getElementById("sendBtn");
if (form) {
form.onsubmit = function (e) {
e.preventDefault();
sendMessage();
return false;
};
}
if (input) {
input.addEventListener("input", handleMentionInput);
input.onkeydown = function (e) {
if (handleMentionKeydown(e)) {
return;
}
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
};
}
2025-12-03 18:42:22 -03:00
if (sendBtn) {
sendBtn.onclick = function (e) {
e.preventDefault();
sendMessage();
};
}
document.addEventListener("click", function (e) {
if (
!e.target.closest("#mentionDropdown") &&
!e.target.closest("#messageInput")
) {
hideMentionDropdown();
}
});
}
setupEventHandlers();
initChat();
console.log("Chat module initialized with @ mentions support");
})();
</script>