// Chat UI Module - Threat-aware chat with GPT
(function () {
const { state } = APP.core;
const { $ } = APP.core.utils;
const { log } = APP.ui.logging;
let chatHistory = [];
/**
* Initialize the chat module.
*/
function init() {
const chatInput = $("#chatInput");
const chatSend = $("#chatSend");
if (chatSend) {
chatSend.addEventListener("click", sendMessage);
}
if (chatInput) {
chatInput.addEventListener("keydown", (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
}
// Toggle chat panel
const chatToggle = $("#chatToggle");
const chatPanel = $("#chatPanel");
if (chatToggle && chatPanel) {
chatToggle.addEventListener("click", () => {
chatPanel.classList.toggle("collapsed");
chatToggle.textContent = chatPanel.classList.contains("collapsed")
? "▼ Chat"
: "▲ Close Chat";
});
}
}
/**
* Send a chat message about current threats.
*/
async function sendMessage() {
const chatInput = $("#chatInput");
const chatMessages = $("#chatMessages");
if (!chatInput || !chatMessages) return;
const question = chatInput.value.trim();
if (!question) {
log("Please enter a question.", "w");
return;
}
// Check if we have detections
if (!state.detections || state.detections.length === 0) {
appendMessage("system", "No detections yet. Run Detect first to analyze the scene.");
return;
}
// Add user message to UI
appendMessage("user", question);
chatInput.value = "";
chatInput.disabled = true;
// Show loading indicator
const loadingId = appendMessage("assistant", "Analyzing scene...", true);
try {
const response = await APP.api.client.chatAboutThreats(question, state.detections);
// Remove loading message
removeMessage(loadingId);
if (response.response) {
appendMessage("assistant", response.response);
chatHistory.push({ role: "user", content: question });
chatHistory.push({ role: "assistant", content: response.response });
} else if (response.error || response.detail) {
appendMessage("system", `Error: ${response.error || response.detail}`);
}
} catch (err) {
removeMessage(loadingId);
appendMessage("system", `Failed to get response: ${err.message}`);
log(`Chat error: ${err.message}`, "e");
} finally {
chatInput.disabled = false;
chatInput.focus();
}
}
/**
* Append a message to the chat display.
*/
function appendMessage(role, content, isLoading = false) {
const chatMessages = $("#chatMessages");
if (!chatMessages) return null;
const msgId = `msg-${Date.now()}`;
const msgDiv = document.createElement("div");
msgDiv.className = `chat-message chat-${role}`;
msgDiv.id = msgId;
if (isLoading) {
msgDiv.classList.add("loading");
}
// Format content with line breaks
const formatted = content.replace(/\n/g, "
");
const icon = role === "user" ? "YOU" : role === "assistant" ? "TAC" : "SYS";
msgDiv.innerHTML = `${icon}${formatted}`;
chatMessages.appendChild(msgDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
return msgId;
}
/**
* Remove a message by ID.
*/
function removeMessage(msgId) {
const msg = document.getElementById(msgId);
if (msg) msg.remove();
}
/**
* Clear chat history.
*/
function clearChat() {
const chatMessages = $("#chatMessages");
if (chatMessages) {
chatMessages.innerHTML = "";
}
chatHistory = [];
}
// Export
APP.ui = APP.ui || {};
APP.ui.chat = {
init,
sendMessage,
clearChat
};
// Auto-init on DOMContentLoaded
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();