Spaces:
Paused
Paused
File size: 8,207 Bytes
aceb1b2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 | /**
* Agent Chat UI
*
* Handles the interactive chat interface for agent testing annotation.
* Communicates with /agent_chat/ routes on the Flask backend.
*/
(function() {
'use strict';
var chatPanel = null;
var messagesContainer = null;
var inputField = null;
var sendBtn = null;
var finishBtn = null;
var stepCounter = null;
var isSending = false;
/**
* Initialize the chat UI on page load.
* Checks for an existing session and restores state if needed.
*/
function agentChatInit() {
chatPanel = document.getElementById('agent-chat-panel');
if (!chatPanel) return;
messagesContainer = document.getElementById('agent-chat-messages');
inputField = document.getElementById('agent-chat-input');
sendBtn = document.getElementById('agent-chat-send-btn');
finishBtn = document.getElementById('agent-chat-finish-btn');
stepCounter = document.getElementById('agent-chat-step-counter');
// Check if chat is in active mode (data-chat-active on container)
var container = chatPanel.closest('[data-chat-active]');
if (container && container.getAttribute('data-chat-active') === 'false') {
// Conversation is already finalized, nothing to do
return;
}
// Disable annotation forms while chat is active
document.body.classList.add('agent-chat-active');
// Set up Enter key handler
if (inputField) {
inputField.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
agentChatSend();
}
});
}
// Check for existing session (handles page refresh)
fetch('/agent_chat/status', {
method: 'GET',
credentials: 'same-origin'
})
.then(function(resp) { return resp.json(); })
.then(function(data) {
if (data.active) {
// Restore messages from existing session
restoreMessages(data.messages || []);
updateStepCounter(data.step_count || 0, data.max_steps || 20);
}
})
.catch(function(err) {
console.log('No active agent session:', err);
});
}
/**
* Send a message to the agent.
*/
function agentChatSend() {
if (isSending || !inputField) return;
var message = inputField.value.trim();
if (!message) return;
isSending = true;
inputField.value = '';
inputField.disabled = true;
if (sendBtn) sendBtn.disabled = true;
if (finishBtn) finishBtn.disabled = true;
// Clear placeholder
var placeholder = messagesContainer.querySelector('.agent-chat-placeholder');
if (placeholder) placeholder.remove();
// Add user message
appendMessage('user', message);
// Show typing indicator
var typingEl = showTypingIndicator();
fetch('/agent_chat/send', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: message })
})
.then(function(resp) { return resp.json(); })
.then(function(data) {
removeTypingIndicator(typingEl);
if (data.error) {
appendMessage('error', data.error);
} else {
appendMessage('agent', data.content || '');
updateStepCounter(data.step_count || 0, data.max_steps || 20);
}
})
.catch(function(err) {
removeTypingIndicator(typingEl);
appendMessage('error', 'Failed to send message: ' + err.message);
})
.finally(function() {
isSending = false;
inputField.disabled = false;
if (sendBtn) sendBtn.disabled = false;
if (finishBtn) finishBtn.disabled = false;
inputField.focus();
});
}
/**
* Finish the chat and transition to annotation mode.
*/
function agentChatFinish() {
if (isSending) return;
// Confirm if there are no messages
var messages = messagesContainer.querySelectorAll('.agent-chat-message');
if (messages.length === 0) {
if (!confirm('No messages have been sent. Are you sure you want to finish?')) {
return;
}
}
isSending = true;
if (sendBtn) sendBtn.disabled = true;
if (finishBtn) finishBtn.disabled = true;
inputField.disabled = true;
fetch('/agent_chat/finish', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' }
})
.then(function(resp) { return resp.json(); })
.then(function(data) {
if (data.error) {
appendMessage('error', data.error);
isSending = false;
if (sendBtn) sendBtn.disabled = false;
if (finishBtn) finishBtn.disabled = false;
inputField.disabled = false;
} else {
// Reload page to show the trace display with annotation forms
window.location.reload();
}
})
.catch(function(err) {
appendMessage('error', 'Failed to finish: ' + err.message);
isSending = false;
if (sendBtn) sendBtn.disabled = false;
if (finishBtn) finishBtn.disabled = false;
inputField.disabled = false;
});
}
/**
* Append a message bubble to the chat.
*/
function appendMessage(role, content) {
var msgDiv = document.createElement('div');
msgDiv.className = 'agent-chat-message ' + role;
var senderDiv = document.createElement('div');
senderDiv.className = 'agent-chat-sender';
senderDiv.textContent = role === 'user' ? 'You' : role === 'agent' ? 'Agent' : '';
var bubbleDiv = document.createElement('div');
bubbleDiv.className = 'agent-chat-bubble';
bubbleDiv.textContent = content;
if (role !== 'error') {
msgDiv.appendChild(senderDiv);
}
msgDiv.appendChild(bubbleDiv);
messagesContainer.appendChild(msgDiv);
// Scroll to bottom
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
/**
* Restore messages from session state (after page refresh).
*/
function restoreMessages(messages) {
// Clear placeholder
var placeholder = messagesContainer.querySelector('.agent-chat-placeholder');
if (placeholder) placeholder.remove();
for (var i = 0; i < messages.length; i++) {
var msg = messages[i];
appendMessage(msg.role, msg.content);
}
}
/**
* Show a typing indicator.
*/
function showTypingIndicator() {
var el = document.createElement('div');
el.className = 'agent-chat-typing';
el.innerHTML = '<div class="dot"></div><div class="dot"></div><div class="dot"></div>';
messagesContainer.appendChild(el);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
return el;
}
/**
* Remove a typing indicator element.
*/
function removeTypingIndicator(el) {
if (el && el.parentNode) {
el.parentNode.removeChild(el);
}
}
/**
* Update the step counter display.
*/
function updateStepCounter(current, max) {
if (stepCounter) {
stepCounter.textContent = 'Step ' + current + ' / ' + max;
}
}
// Expose functions globally for onclick handlers
window.agentChatSend = agentChatSend;
window.agentChatFinish = agentChatFinish;
window.agentChatInit = agentChatInit;
// Initialize on DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', agentChatInit);
} else {
agentChatInit();
}
})();
|