Lab 29: Building a Simple Custom Chat UI Challenge
Goal
In this lab, you will build a simple, standalone HTML file with JavaScript that acts as a custom chat client for an ADK agent, demonstrating how to connect a UI to the ADK's native API server.
Step 1: Create the Agent Project
-
Create and navigate to the agent project:
adk create ui-agent
cd ui-agentChoose the Programmatic (Python script) option.
-
Implement the agent: Open
agent.pyand replace its contents with this simple agent:from google.adk.agents import Agent
root_agent = Agent(
model="gemini-2.5-flash",
name="ui_agent",
instruction="You are a helpful and friendly assistant.",
) -
Set up your
.envfile.
Step 2: Create the Custom HTML/JavaScript Client
Exercise: Create an index.html file. A skeleton is provided below. Your task is to complete the JavaScript logic inside the submit event listener to handle the streaming chat.
<!-- In index.html (Starter Code) -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ADK Custom UI</title>
<style>
body { font-family: sans-serif; display: flex; flex-direction: column; height: 100vh; margin: 0; }
#chat-container { flex: 1; overflow-y: auto; padding: 20px; }
.message { margin-bottom: 15px; padding: 10px; border-radius: 10px; max-width: 80%; }
.user { background-color: #dcf8c6; align-self: flex-end; }
.assistant { background-color: #f1f0f0; align-self: flex-start; }
#input-form { display: flex; padding: 20px; border-top: 1px solid #ccc; }
input { flex: 1; padding: 10px; border-radius: 5px; }
button { padding: 10px 20px; margin-left: 10px; border-radius: 5px; }
</style>
</head>
<body>
<div id="chat-container"></div>
<form id="input-form">
<input type="text" id="message-input" placeholder="Type your message..." autocomplete="off">
<button type="submit">Send</button>
</form>
<script>
const chatContainer = document.getElementById('chat-container');
const inputForm = document.getElementById('input-form');
const messageInput = document.getElementById('message-input');
// This is a simple client-side session ID for the lab.
// In a real app, you would manage this more robustly.
const sessionId = `session-${Date.now()}`;
inputForm.addEventListener('submit', async (e) => {
e.preventDefault();
const query = messageInput.value.trim();
if (!query) return;
addMessage(query, 'user');
messageInput.value = '';
const assistantMessageDiv = addMessage('...', 'assistant');
try {
// TODO: 1. Use the `fetch` API to make a POST request to the
// ADK's `/run_sse` endpoint (http://localhost:8080/run_sse).
const response = await fetch('http://localhost:8080/run_sse', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
app_name: "ui-agent",
session_id: sessionId,
new_message: {
role: "user",
parts: [{ "text": query }]
}
})
});
// TODO: 2. Get the `reader` from the response body to process the stream.
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullResponse = '';
// TODO: 3. Write a `while (true)` loop to read chunks from the stream.
// - Inside the loop, get the `value` and `done` from `reader.read()`.
// - If `done`, break the loop.
// - Decode the chunk, split it by newlines, and parse the SSE `data:` lines.
// - Extract the `text` from the event data and append it to the UI.
} catch (error) {
assistantMessageDiv.textContent = 'Error: Could not connect to the agent.';
console.error('Error:', error);
}
});
function addMessage(text, role) {
const messageDiv = document.createElement('div');
messageDiv.classList.add('message', role);
messageDiv.textContent = text;
chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
return messageDiv;
}
</script>
</body>
</html>
Step 3: Run the Full-Stack Application
- Terminal 1 (Agent Server): In the
ui-agentdirectory, runadk api_server ui-agent.- Note on CORS: The ADK API server automatically handles Cross-Origin Resource Sharing (CORS), allowing your web page on port 8081 to make requests to your agent on port 8080.
- Terminal 2 (Client Server): In the same directory, run
python3 -m http.server 8081.
Step 4: Test Your Custom UI
- Open the Client: In your browser, navigate to
http://localhost:8081. - Chat with the Agent: Send a message and watch the agent's response stream in.
Having Trouble?
If you get stuck, you can find the complete, working code in the lab-solution.md file.
Lab Summary
You have successfully built a full-stack agent application with a custom front-end. You have learned:
- How to run an ADK agent as a backend service using
adk api_server. - How to connect a custom JavaScript client to the ADK's native
/run_ssestreaming endpoint. - The basic principles of handling streaming responses in a web UI.
Self-Reflection Questions
- The ADK server sends events using Server-Sent Events (SSE). What are the advantages of SSE for a chat application compared to a traditional request-response model?
- Our simple client generates a new
sessionIdevery time the page loads. What problems would this cause in a real application, and how would you solve it (e.g., using cookies orlocalStorage)? - This lab uses the native ADK API. What are the potential benefits of using a higher-level framework like the AG-UI Protocol and CopilotKit for a more complex, production-ready application?
🕵️ Hidden Solution 🕵️
Looking for the solution? Here's a hint (Base64 decode me):
L2RvYy1hZGstdHJhaW5pbmcvbW9kdWxlMjktdWktaW50ZWdyYXRpb24taW50cm8vbGFiLXNvbHV0aW9u
The direct link is: Lab Solution