adk-typescript
Version:
TypeScript port of Google's Agent Development Kit (ADK)
303 lines (256 loc) • 9.23 kB
JavaScript
/**
* ADK Web UI - Main Client Script
*/
class AdkChatApp {
constructor() {
// Initialize properties
this.socket = null;
this.agents = [];
this.selectedAgent = null;
this.currentSession = null;
this.isConnected = false;
this.isThinking = false;
// Get DOM elements
this.agentSelect = document.getElementById('agent-select');
this.connectBtn = document.getElementById('connect-btn');
this.messagesContainer = document.getElementById('messages');
this.userInput = document.getElementById('user-input');
this.sendBtn = document.getElementById('send-btn');
this.newSessionBtn = document.getElementById('new-session-btn');
this.sessionDetails = document.getElementById('session-details');
this.agentDetails = document.getElementById('agent-details');
// Set up event listeners
this.connectBtn.addEventListener('click', () => this.connectToAgent());
this.sendBtn.addEventListener('click', () => this.sendMessage());
this.newSessionBtn.addEventListener('click', () => this.createNewSession());
this.userInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
});
// Initialize
this.initialize();
}
async initialize() {
try {
// Fetch available agents
const response = await fetch('/list-apps');
if (response.ok) {
const data = await response.json();
this.agents = data.agents || []; // Handle the agents array structure from the API
this.populateAgentDropdown();
} else {
this.showError('Failed to load agents');
}
} catch (error) {
this.showError('Error initializing: ' + error.message);
}
}
populateAgentDropdown() {
this.agentSelect.innerHTML = '';
this.agents.forEach(agent => {
const option = document.createElement('option');
option.value = agent.name;
option.textContent = agent.name;
this.agentSelect.appendChild(option);
});
}
async connectToAgent() {
const agentName = this.agentSelect.value;
if (!agentName) {
this.showError('Please select an agent');
return;
}
this.selectedAgent = { name: agentName };
this.connectBtn.disabled = true;
this.connectBtn.textContent = 'Connecting...';
try {
// Create a new session
const response = await fetch(`/apps/${agentName}/users/web-user/sessions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ state: {} })
});
if (response.ok) {
this.currentSession = await response.json();
this.setupSocketConnection();
this.updateSessionInfo();
this.updateAgentInfo();
this.clearMessages();
this.sendBtn.disabled = false;
this.isConnected = true;
this.addSystemMessage(`Connected to agent: ${agentName}`);
} else {
throw new Error('Failed to create session');
}
} catch (error) {
this.showError('Connection error: ' + error.message);
this.connectBtn.disabled = false;
this.connectBtn.textContent = 'Connect';
}
}
setupSocketConnection() {
if (this.socket) {
this.socket.disconnect();
}
// Connect to the socket server
this.socket = io();
this.socket.on('connect', () => {
console.log('Socket connected');
this.connectBtn.textContent = 'Connected';
});
this.socket.on('error', (error) => {
this.showError('Socket error: ' + error.message);
});
this.socket.on('thinking', () => {
this.isThinking = true;
this.addThinkingIndicator();
});
this.socket.on('response_chunk', (data) => {
this.isThinking = false;
this.removeThinkingIndicator();
this.updateOrAddAgentMessage(data.text, data.author, data.partial);
});
this.socket.on('response_complete', () => {
this.isThinking = false;
this.removeThinkingIndicator();
});
this.socket.on('function_calls', (data) => {
console.log('Function calls:', data);
// Display function calls in the UI if needed
});
this.socket.on('function_responses', (data) => {
console.log('Function responses:', data);
// Display function responses in the UI if needed
});
this.socket.on('disconnect', () => {
console.log('Socket disconnected');
this.isConnected = false;
this.sendBtn.disabled = true;
this.connectBtn.disabled = false;
this.connectBtn.textContent = 'Connect';
});
// Initialize the agent by sending information to the socket
this.socket.emit('initialize_agent', {
agentPath: this.selectedAgent.name
});
}
async sendMessage() {
if (!this.isConnected || !this.currentSession || this.isThinking) return;
const message = this.userInput.value.trim();
if (!message) return;
this.addUserMessage(message);
this.userInput.value = '';
if (this.socket) {
this.socket.emit('message', { message });
}
}
async createNewSession() {
if (!this.selectedAgent) {
this.showError('Please select and connect to an agent first');
return;
}
try {
const response = await fetch(`/apps/${this.selectedAgent.name}/users/web-user/sessions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ state: {} })
});
if (response.ok) {
this.currentSession = await response.json();
this.updateSessionInfo();
this.clearMessages();
this.addSystemMessage('New session created');
} else {
throw new Error('Failed to create new session');
}
} catch (error) {
this.showError('Error creating new session: ' + error.message);
}
}
addUserMessage(text) {
const messageDiv = document.createElement('div');
messageDiv.className = 'message user-message';
messageDiv.textContent = text;
this.messagesContainer.appendChild(messageDiv);
this.scrollToBottom();
}
addAgentMessage(text, agent) {
const messageDiv = document.createElement('div');
messageDiv.className = 'message agent-message';
messageDiv.dataset.agent = agent;
messageDiv.textContent = text;
this.messagesContainer.appendChild(messageDiv);
this.scrollToBottom();
return messageDiv;
}
updateOrAddAgentMessage(text, agent, isPartial) {
const existingPartialMessage = this.messagesContainer.querySelector(`.message.agent-message[data-partial="true"][data-agent="${agent}"]`);
if (existingPartialMessage) {
existingPartialMessage.textContent = text;
if (!isPartial) {
existingPartialMessage.dataset.partial = 'false';
}
} else {
const messageDiv = this.addAgentMessage(text, agent);
messageDiv.dataset.partial = isPartial ? 'true' : 'false';
}
this.scrollToBottom();
}
addSystemMessage(text) {
const messageDiv = document.createElement('div');
messageDiv.className = 'message system-message';
messageDiv.textContent = text;
this.messagesContainer.appendChild(messageDiv);
this.scrollToBottom();
}
addThinkingIndicator() {
// Remove any existing thinking indicators
this.removeThinkingIndicator();
// Add new thinking indicator
const thinkingDiv = document.createElement('div');
thinkingDiv.className = 'message agent-message thinking-message';
const loadingSpan = document.createElement('span');
loadingSpan.className = 'loading';
thinkingDiv.appendChild(loadingSpan);
const textSpan = document.createElement('span');
textSpan.textContent = 'Thinking...';
thinkingDiv.appendChild(textSpan);
this.messagesContainer.appendChild(thinkingDiv);
this.scrollToBottom();
}
removeThinkingIndicator() {
const thinkingMessage = this.messagesContainer.querySelector('.thinking-message');
if (thinkingMessage) {
thinkingMessage.remove();
}
}
updateSessionInfo() {
if (!this.currentSession) return;
this.sessionDetails.innerHTML = `
<p><strong>Session ID:</strong> ${this.currentSession.id}</p>
<p><strong>User ID:</strong> ${this.currentSession.userId}</p>
`;
}
updateAgentInfo() {
if (!this.selectedAgent) return;
this.agentDetails.innerHTML = `
<p><strong>Agent:</strong> ${this.selectedAgent.name}</p>
`;
}
clearMessages() {
this.messagesContainer.innerHTML = '';
}
scrollToBottom() {
this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
}
showError(message) {
console.error(message);
this.addSystemMessage(`Error: ${message}`);
}
}
// Initialize the app when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', () => {
new AdkChatApp();
});