Add chat modal to HUD for responding to chat tasks

- When accepting a chat task, automatically opens chat modal
- Chat modal shows message history and allows sending replies
- Auto-polls for new messages every 2 seconds
- End Chat button to complete the chat task

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John Mizerek 2026-02-08 15:22:19 -08:00
parent 641b238de8
commit 8108924dcd
2 changed files with 366 additions and 0 deletions

View file

@ -22,6 +22,12 @@ const HUD = {
isConnected: true,
businessName: '',
// Chat state
chatTaskId: null,
chatMessages: [],
chatPollInterval: null,
lastMessageId: 0,
// Category names (will be loaded from API)
categories: {
1: { name: 'Orders', color: '#ef4444' },
@ -343,6 +349,11 @@ const HUD = {
this.tasks = this.tasks.filter(t => t.TaskID !== task.TaskID);
this.renderTasks();
this.showFeedback('Task accepted!', 'success');
// If it's a chat task, open the chat modal
if (this.isChatTask(task)) {
this.openChat(task);
}
} else {
this.showFeedback(data.ERROR || 'Failed to accept', 'error');
}
@ -368,6 +379,190 @@ const HUD = {
} else {
indicator.classList.add('disconnected');
}
},
// Check if task is a chat task
isChatTask(task) {
if (!task) return false;
const name = (task.TaskTypeName || task.Title || '').toLowerCase();
return name.includes('chat');
},
// Open chat modal for a task
openChat(task) {
this.chatTaskId = task.TaskID;
this.chatMessages = [];
this.lastMessageId = 0;
document.getElementById('chatTitle').textContent = task.Title || 'Chat';
document.getElementById('chatMessages').innerHTML = '<div class="chat-loading">Loading messages...</div>';
document.getElementById('chatOverlay').classList.add('visible');
document.getElementById('chatInput').focus();
// Load messages immediately and start polling
this.loadMessages();
this.chatPollInterval = setInterval(() => this.loadMessages(), 2000);
},
// Close chat modal
closeChat() {
document.getElementById('chatOverlay').classList.remove('visible');
if (this.chatPollInterval) {
clearInterval(this.chatPollInterval);
this.chatPollInterval = null;
}
this.chatTaskId = null;
this.chatMessages = [];
this.lastMessageId = 0;
},
// Load chat messages
async loadMessages() {
if (!this.chatTaskId) return;
try {
const response = await fetch('/api/chat/getMessages.cfm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
TaskID: this.chatTaskId,
AfterMessageID: this.lastMessageId
})
});
const data = await response.json();
if (data.OK) {
const messages = data.MESSAGES || [];
// If first load, replace all; otherwise append new messages
if (this.lastMessageId === 0) {
this.chatMessages = messages;
} else if (messages.length > 0) {
this.chatMessages = [...this.chatMessages, ...messages];
}
// Update last message ID
if (messages.length > 0) {
this.lastMessageId = messages[messages.length - 1].MessageID;
}
this.renderMessages();
// Check if chat was closed
if (data.CHAT_CLOSED) {
this.showFeedback('Chat has ended', 'info');
this.closeChat();
}
}
} catch (err) {
console.error('[HUD] Load messages error:', err);
}
},
// Render chat messages
renderMessages() {
const container = document.getElementById('chatMessages');
if (this.chatMessages.length === 0) {
container.innerHTML = '<div class="chat-loading">No messages yet. Start the conversation!</div>';
return;
}
container.innerHTML = this.chatMessages.map(msg => {
const isStaff = msg.SenderType === 'staff' || msg.SenderType === 'worker';
const time = new Date(msg.CreatedOn).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
return `
<div class="chat-message ${isStaff ? 'staff' : 'customer'}">
<div class="sender">${msg.SenderName || (isStaff ? 'Staff' : 'Customer')}</div>
<div class="text">${this.escapeHtml(msg.MessageBody)}</div>
<div class="time">${time}</div>
</div>
`;
}).join('');
// Scroll to bottom
container.scrollTop = container.scrollHeight;
},
// Send a chat message
async sendMessage() {
const input = document.getElementById('chatInput');
const message = input.value.trim();
if (!message || !this.chatTaskId) return;
input.value = '';
input.disabled = true;
try {
const response = await fetch('/api/chat/sendMessage.cfm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
TaskID: this.chatTaskId,
Message: message,
SenderType: 'staff'
})
});
const data = await response.json();
if (data.OK) {
// Add message locally for immediate feedback
this.chatMessages.push({
MessageID: data.MessageID || Date.now(),
MessageBody: message,
SenderType: 'staff',
SenderName: 'Staff',
CreatedOn: new Date().toISOString()
});
this.renderMessages();
} else {
this.showFeedback('Failed to send message', 'error');
}
} catch (err) {
console.error('[HUD] Send message error:', err);
this.showFeedback('Failed to send message', 'error');
}
input.disabled = false;
input.focus();
},
// End the chat (complete the task)
async endChat() {
if (!this.chatTaskId) return;
if (!confirm('End this chat?')) return;
try {
const response = await fetch('/api/tasks/completeChat.cfm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ TaskID: this.chatTaskId })
});
const data = await response.json();
if (data.OK) {
this.showFeedback('Chat ended', 'success');
this.closeChat();
} else {
this.showFeedback(data.ERROR || 'Failed to end chat', 'error');
}
} catch (err) {
console.error('[HUD] End chat error:', err);
this.showFeedback('Failed to end chat', 'error');
}
},
// Escape HTML for safe rendering
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
};

View file

@ -334,6 +334,157 @@
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Chat modal */
.chat-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.95);
z-index: 1100;
justify-content: center;
align-items: center;
padding: 20px;
}
.chat-overlay.visible {
display: flex;
}
.chat-container {
background: #1a1a1a;
border-radius: 12px;
width: 100%;
max-width: 500px;
height: 80vh;
max-height: 600px;
display: flex;
flex-direction: column;
border: 1px solid #333;
}
.chat-header {
padding: 16px;
border-bottom: 1px solid #333;
display: flex;
justify-content: space-between;
align-items: center;
}
.chat-header h3 {
margin: 0;
color: #fff;
font-size: 18px;
}
.chat-close-btn {
background: #333;
border: none;
color: #fff;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.chat-message {
max-width: 80%;
padding: 10px 14px;
border-radius: 12px;
font-size: 14px;
line-height: 1.4;
}
.chat-message.customer {
align-self: flex-start;
background: #333;
color: #fff;
}
.chat-message.staff {
align-self: flex-end;
background: #2196f3;
color: #fff;
}
.chat-message .sender {
font-size: 11px;
opacity: 0.7;
margin-bottom: 4px;
}
.chat-message .time {
font-size: 10px;
opacity: 0.5;
margin-top: 4px;
text-align: right;
}
.chat-input-area {
padding: 16px;
border-top: 1px solid #333;
display: flex;
gap: 12px;
}
.chat-input {
flex: 1;
background: #333;
border: none;
border-radius: 8px;
padding: 12px 16px;
color: #fff;
font-size: 14px;
outline: none;
}
.chat-input::placeholder {
color: #666;
}
.chat-send-btn {
background: #22c55e;
border: none;
border-radius: 8px;
padding: 12px 20px;
color: #fff;
font-size: 14px;
font-weight: 600;
cursor: pointer;
}
.chat-send-btn:active {
transform: scale(0.98);
}
.chat-loading {
text-align: center;
color: #666;
padding: 20px;
}
.chat-end-btn {
background: #ef4444;
border: none;
color: #fff;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
margin-left: 8px;
}
</style>
</head>
<body>
@ -385,6 +536,26 @@
<div class="status-indicator" id="statusIndicator"></div>
<button class="restore-btn" id="restoreBtn" onclick="toggleFullscreen()"></button>
<!-- Chat Modal -->
<div class="chat-overlay" id="chatOverlay">
<div class="chat-container">
<div class="chat-header">
<h3 id="chatTitle">Chat</h3>
<div>
<button class="chat-close-btn" onclick="HUD.closeChat()">Close</button>
<button class="chat-end-btn" onclick="HUD.endChat()">End Chat</button>
</div>
</div>
<div class="chat-messages" id="chatMessages">
<div class="chat-loading">Loading messages...</div>
</div>
<div class="chat-input-area">
<input type="text" class="chat-input" id="chatInput" placeholder="Type a message..." onkeypress="if(event.key==='Enter')HUD.sendMessage()">
<button class="chat-send-btn" onclick="HUD.sendMessage()">Send</button>
</div>
</div>
</div>
<script src="hud.js"></script>
</body>
</html>