dealspace/cmd/server/website/website/chat.js

181 lines
5.8 KiB
JavaScript

// Aria Chat Widget - Dealspace Product Assistant
(function() {
'use strict';
// Generate or retrieve session ID
function getSessionId() {
let sessionId = sessionStorage.getItem('aria_session_id');
if (!sessionId) {
sessionId = 'aria_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
sessionStorage.setItem('aria_session_id', sessionId);
}
return sessionId;
}
// Chat state
const state = {
isOpen: false,
isLoading: false,
history: [],
sessionId: getSessionId()
};
// Create chat widget HTML
function createWidget() {
// Chat button
const button = document.createElement('button');
button.id = 'aria-chat-button';
button.setAttribute('aria-label', 'Open chat with Aria');
button.innerHTML = `
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
</svg>
`;
// Chat panel
const panel = document.createElement('div');
panel.id = 'aria-chat-panel';
panel.innerHTML = `
<div id="aria-chat-header">
<div id="aria-avatar"><span>A</span></div>
<div id="aria-header-text">
<h3>Aria</h3>
<p>Dealspace Assistant</p>
</div>
<button id="aria-close-btn" aria-label="Close chat">&times;</button>
</div>
<div id="aria-chat-messages"></div>
<div id="aria-chat-input">
<input type="text" id="aria-message-input" placeholder="Ask about Dealspace..." autocomplete="off">
<button id="aria-send-btn" aria-label="Send message">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
</svg>
</button>
</div>
`;
document.body.appendChild(button);
document.body.appendChild(panel);
// Event listeners
button.addEventListener('click', toggleChat);
document.getElementById('aria-close-btn').addEventListener('click', toggleChat);
document.getElementById('aria-send-btn').addEventListener('click', sendMessage);
document.getElementById('aria-message-input').addEventListener('keypress', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
}
function toggleChat() {
const panel = document.getElementById('aria-chat-panel');
state.isOpen = !state.isOpen;
if (state.isOpen) {
panel.classList.add('open');
// Show welcome message if no history
if (state.history.length === 0) {
addMessage("Hi, I'm Aria! I can answer questions about Dealspace — features, pricing, security, or how it works. What would you like to know?", 'assistant');
}
document.getElementById('aria-message-input').focus();
} else {
panel.classList.remove('open');
}
}
function addMessage(content, role) {
const messagesContainer = document.getElementById('aria-chat-messages');
const messageDiv = document.createElement('div');
messageDiv.className = 'aria-message ' + role;
messageDiv.textContent = content;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
// Store in history (exclude welcome message)
if (role !== 'assistant' || state.history.length > 0 || content !== "Hi, I'm Aria! I can answer questions about Dealspace — features, pricing, security, or how it works. What would you like to know?") {
state.history.push({ role: role, content: content });
// Keep only last 6 messages
if (state.history.length > 6) {
state.history = state.history.slice(-6);
}
}
}
function showTyping() {
const messagesContainer = document.getElementById('aria-chat-messages');
const typingDiv = document.createElement('div');
typingDiv.id = 'aria-typing-indicator';
typingDiv.className = 'aria-typing';
typingDiv.innerHTML = '<span></span><span></span><span></span>';
messagesContainer.appendChild(typingDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
function hideTyping() {
const typingIndicator = document.getElementById('aria-typing-indicator');
if (typingIndicator) {
typingIndicator.remove();
}
}
async function sendMessage() {
const input = document.getElementById('aria-message-input');
const sendBtn = document.getElementById('aria-send-btn');
const message = input.value.trim();
if (!message || state.isLoading) return;
// Add user message
addMessage(message, 'user');
input.value = '';
// Show loading state
state.isLoading = true;
sendBtn.disabled = true;
showTyping();
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_id: state.sessionId,
message: message,
history: state.history.slice(0, -1) // Exclude the message we just added
})
});
hideTyping();
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Something went wrong');
}
const data = await response.json();
addMessage(data.reply, 'assistant');
} catch (error) {
hideTyping();
console.error('Chat error:', error);
addMessage("Sorry, I'm having trouble connecting. Please try again in a moment.", 'assistant');
} finally {
state.isLoading = false;
sendBtn.disabled = false;
input.focus();
}
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', createWidget);
} else {
createWidget();
}
})();