codebook / potato /static /agent-chat.js
davidjurgens's picture
Deploy: Potato — Codebook Annotation
aceb1b2 verified
Raw
History Blame Contribute Delete
8.21 kB
/**
* 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();
}
})();