gemini/mcp/adk_mcp_app/static/index.html (466 lines of code) (raw):
<!doctype html>
<html>
<head>
<title>ADK MCP App - Alternating Messages (Debug)</title>
<style>
/* CSS Styles remain the same as the previous 'alternating' version */
/* Apply Arial font to the whole page */
body {
font-family: Arial, sans-serif;
line-height: 1.6;
}
/* Style for the header container (logo + title) */
.header-container {
display: flex;
align-items: center;
margin-bottom: 1em;
}
/* Style for the logo image */
.logo {
height: 40px;
width: auto;
margin-right: 15px;
}
/* Style for the main heading */
.header-container h1 {
margin: 0;
}
#sendButton {
background-color: #28a745;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
#sendButton:hover:not(:disabled) {
background-color: #218838;
}
#sendButton:disabled {
background-color: #a3d9b1;
color: #f0f0f0;
cursor: not-allowed;
}
#messages {
height: 800px;
overflow-y: auto;
border: 1px solid #ccc;
padding: 10px;
background-color: #f9f9f9;
/* Add some spacing between messages */
display: flex;
flex-direction: column;
gap: 0.5em;
}
/* Remove default margins from paragraphs inside messages */
#messages p {
margin: 0;
/* Resetting margins */
}
/* Style for user messages */
#messages p.user-message {
color: #0056b3;
background-color: #e7f3ff;
padding: 8px 8px 8px 12px;
border-radius: 5px;
position: relative;
align-self: flex-end;
/* Align user messages to the right */
max-width: 80%;
/* Optional: limit width */
word-wrap: break-word;
/* Ensure long words break */
}
/* Add human icon and [User] prefix */
#messages p.user-message::before {
content: "👤 [User]: ";
/* Added space */
font-weight: bold;
margin-right: 5px;
/* Reduced margin */
}
/* Base style for server messages container (agent responses AND system status) */
#messages p.server-message-block {
color: #020202;
padding: 8px 8px 8px 12px;
background-color: #fff;
border: 1px solid #eee;
border-radius: 5px;
position: relative;
align-self: flex-start;
/* Align server messages to the left */
max-width: 80%;
/* Optional: limit width */
word-wrap: break-word;
/* Ensure long words break */
}
/* Add robot icon ONLY to server messages that are NOT system status messages */
#messages p.server-message-block:not(.system-status-message)::before {
content: "🤖[Agent]: ";
/* Robot emoji */
font-weight: bold;
margin-right: 8px;
/* Space between icon and message content */
display: inline-block;
/* Helps with alignment */
vertical-align: top;
/* Align icon with the top of the text block */
float: left;
/* Float icon left */
}
/* Adjust content padding for server messages with icon */
#messages p.server-message-block:not(.system-status-message) {
padding-left: 30px;
/* Make space for the floated icon */
}
/* Specific styles for system status messages (they still use server-message-block for base style) */
#messages p.system-status-message {
/* Center align system messages and use full width */
align-self: center;
max-width: 100%;
text-align: center;
font-style: italic;
color: #555;
background-color: #f0f0f0;
/* Slightly different background */
border: none;
padding-left: 12px;
/* Reset padding */
}
/* Keep specific color styles using spans inside the p tags */
.connection-open-text {
color: green;
font-weight: bold;
}
.connection-closed-text {
color: orange;
font-weight: bold;
}
.error-text {
color: red;
font-weight: bold;
}
/* Basic Markdown styling */
#messages code {
background-color: #eee;
padding: 2px 4px;
border-radius: 3px;
font-family: monospace;
word-wrap: break-word;
}
#messages pre {
background-color: #eee;
padding: 10px;
border-radius: 3px;
overflow-x: auto;
max-width: 100%;
/* Ensure pre doesn't overflow container */
}
#messages blockquote {
border-left: 3px solid #ccc;
padding-left: 10px;
margin-left: 0;
color: #555;
}
#messages ul,
#messages ol {
margin-left: 20px;
/* Keep indentation */
padding-left: 0;
/* Fix potential layout issues with floated icon */
overflow: hidden;
}
/* Ensure Markdown elements inside server messages don't disrupt the icon float */
#messages .server-message-block > *:first-child {
/* No longer needed with float */
}
#messages .server-message-block > pre:first-child,
#messages .server-message-block > ul:first-child,
#messages .server-message-block > ol:first-child,
#messages .server-message-block > blockquote:first-child {
/* No longer needed */
}
</style>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
</head>
<body>
<div class="header-container">
<img
src="https://google.github.io/adk-docs/assets/agent-development-kit.png"
alt="ADK Logo"
class="logo"
title="Agent Development Kit" />
<h1>ADK Agent Integrated with MCP</h1>
</div>
<div id="messages"></div>
<br />
<form id="messageForm">
<label for="message">Message:</label>
<input type="text" id="message" name="message" style="width: 80%" />
<button type="submit" id="sendButton" disabled>Send</button>
</form>
</body>
<script>
// Wrap everything in DOMContentLoaded to ensure elements exist
document.addEventListener("DOMContentLoaded", (event) => {
console.log("DOM fully loaded and parsed");
// --- DOM Elements ---
const messageForm = document.getElementById("messageForm");
const messageInput = document.getElementById("message");
const messagesDiv = document.getElementById("messages");
const sendButton = document.getElementById("sendButton");
// --- Check if elements were found ---
if (!messageForm || !messageInput || !messagesDiv || !sendButton) {
console.error(
"CRITICAL: One or more required DOM elements not found! Check IDs.",
);
alert("Initialization Error: UI elements missing. App cannot start.");
// Add visual error in the message area if possible
if (messagesDiv) {
messagesDiv.innerHTML =
"<p class='server-message-block system-status-message'><span class='error-text'>Initialization Error: UI elements missing.</span></p>";
}
return; // Stop script execution
}
console.log("UI Elements successfully located.");
// --- WebSocket Connection Setup ---
const sessionId = Math.random().toString().substring(10);
const ws_url = "ws://" + window.location.host + "/ws/" + sessionId;
let ws = null; // Initialize ws to null
let reconnectAttempts = 0;
const maxReconnectAttempts = 5; // Limit reconnect attempts
// --- Add Status Message Helper ---
function addStatusMessage(text, typeClass) {
if (!messagesDiv) {
console.error("Cannot add status message, messagesDiv not found");
return;
}
try {
const p = document.createElement("p");
p.classList.add("server-message-block", "system-status-message");
const span = document.createElement("span");
span.className = typeClass; // e.g., 'error-text', 'connection-open-text'
span.textContent = text;
p.appendChild(span);
messagesDiv.appendChild(p);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
} catch (e) {
console.error("Error adding status message:", e);
}
}
// --- WebSocket Handlers ---
function addWebSocketHandlers(webSocketInstance) {
// Clear previous handlers to prevent duplicates on reconnect
webSocketInstance.onopen = null;
webSocketInstance.onmessage = null;
webSocketInstance.onclose = null;
webSocketInstance.onerror = null;
console.log("Cleared previous WebSocket handlers.");
webSocketInstance.onopen = function () {
console.log("WebSocket connection opened successfully.");
reconnectAttempts = 0; // Reset reconnect attempts on successful open
try {
if (sendButton) {
sendButton.disabled = false;
console.log("Send button ENABLED.");
} else {
console.error("Send button element not found in onopen!");
}
addStatusMessage("Connection opened", "connection-open-text");
// Attach the submit handler *only* when connection is open
addSubmitHandler(this);
console.log("Submit handler attached.");
} catch (error) {
console.error("Error during WebSocket onopen execution:", error);
addStatusMessage(
`Error during connection setup: ${error.message}`,
"error-text",
);
if (sendButton) sendButton.disabled = true; // Ensure button is disabled if error occurs
}
};
webSocketInstance.onmessage = function (event) {
// console.log("Received raw data:", event.data); // Keep for debug if needed
try {
const packet = JSON.parse(event.data);
// console.log("Received parsed packet:", packet); // Keep for debug
if (packet.turn_complete && packet.turn_complete === true) {
console.log("Turn complete signal received.");
return;
}
if (packet.message) {
const messageElement = document.createElement("p");
messageElement.classList.add("server-message-block");
try {
const htmlContent = marked.parse(packet.message);
messageElement.innerHTML = htmlContent;
} catch (e) {
console.error("Error parsing Markdown:", e);
const textNode = document.createTextNode(packet.message);
messageElement.appendChild(textNode);
addStatusMessage(
`Markdown parsing error: ${e.message}`,
"error-text",
);
}
if (messagesDiv) {
messagesDiv.appendChild(messageElement);
// console.log("Added new server message block."); // Less verbose logging
messagesDiv.scrollTop = messagesDiv.scrollHeight;
} else {
console.error("Messages container not found in onmessage!");
}
} else {
console.log("Received packet without 'message' field:", packet);
}
} catch (parseError) {
console.error(
"Error parsing incoming WebSocket message:",
parseError,
"Raw data:",
event.data,
);
addStatusMessage(
`Error processing server message: ${parseError.message}`,
"error-text",
);
}
};
webSocketInstance.onclose = function (event) {
console.warn(
`WebSocket connection closed. Code: ${event.code}, Reason: '${
event.reason || "No reason given"
}', Was Clean: ${event.wasClean}`,
);
if (sendButton) {
sendButton.disabled = true;
console.log("Send button DISABLED due to close.");
}
// Remove submit handler when connection is closed
if (messageForm) messageForm.onsubmit = null;
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
const reconnectDelay = 5000 * reconnectAttempts; // Exponential backoff (simple version)
addStatusMessage(
`Connection closed. Attempting reconnect ${reconnectAttempts}/${maxReconnectAttempts} in ${
reconnectDelay / 1000
}s...`,
"connection-closed-text",
);
setTimeout(connectWebSocket, reconnectDelay); // Use the dedicated connect function
} else {
console.error("Max reconnection attempts reached. Giving up.");
addStatusMessage(
"Connection lost. Max reconnection attempts reached. Please reload the page.",
"error-text",
);
}
};
webSocketInstance.onerror = function (error) {
// Note: 'onerror' sends a simple Event object, not usually detailed error info
console.error("WebSocket error occurred:", error);
// Add a generic error message. 'onclose' will usually follow immediately and provide more details/handle reconnect.
addStatusMessage(
"WebSocket error occurred. Check console.",
"error-text",
);
// Do not disable button here, onclose will handle it.
};
console.log(
"WebSocket event handlers attached for:",
webSocketInstance.url,
);
}
// --- Form Submit Handler ---
// Defined outside addWebSocketHandlers so it's created only once
function submitMessageHandler(e) {
e.preventDefault(); // Always prevent default
if (!ws || ws.readyState !== WebSocket.OPEN) {
console.warn(
"Attempted to send message, but WebSocket is not open. State:",
ws ? ws.readyState : "null",
);
addStatusMessage(
"Cannot send message - Connection is not active.",
"error-text",
);
return false;
}
const messageText = messageInput.value.trim();
if (messageText && messagesDiv && messageInput) {
const p = document.createElement("p");
p.textContent = messageText; // Use textContent for safety
p.classList.add("user-message");
messagesDiv.appendChild(p);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
try {
console.log("Sending message:", messageText);
ws.send(messageText);
} catch (error) {
console.error("Error sending message via WebSocket:", error);
addStatusMessage(
`Failed to send message: ${error.message}`,
"error-text",
);
// Optionally add visual indication to the user message itself
const errorSpan = document.createElement("span");
errorSpan.textContent = " (Send Error)";
errorSpan.style.color = "red";
errorSpan.style.fontWeight = "normal";
p.appendChild(errorSpan);
}
messageInput.value = ""; // Clear input field
} else if (!messageText) {
console.log("Empty message submission ignored.");
}
return false; // Prevent default form submission behavior
}
// Function to attach the single submit handler instance
// Now called only from ws.onopen
function addSubmitHandler() {
if (messageForm) {
messageForm.onsubmit = submitMessageHandler;
console.log("Submit handler assigned to form.");
} else {
console.error(
"Message form not found, cannot assign submit handler!",
);
}
}
// --- Initial Connection Function ---
function connectWebSocket() {
console.log(
`Attempting to connect to WebSocket: ${ws_url} (Attempt: ${
reconnectAttempts + 1
})`,
);
// addStatusMessage(`Connecting to server... (Attempt ${reconnectAttempts + 1})`, "connection-closed-text"); // Use orange for connecting status
try {
ws = new WebSocket(ws_url); // Create the WebSocket instance
addWebSocketHandlers(ws); // Attach handlers to the new instance
} catch (error) {
console.error("Error creating WebSocket object:", error);
addStatusMessage(
`Failed to initialize connection: ${error.message}`,
"error-text",
);
// Attempt reconnect after delay if creation fails
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
const reconnectDelay = 5000 * reconnectAttempts;
setTimeout(connectWebSocket, reconnectDelay);
} else {
addStatusMessage(
"Failed to initialize connection after multiple attempts.",
"error-text",
);
}
}
}
// --- Start the application ---
console.log("Starting WebSocket application...");
connectWebSocket(); // Initial connection attempt
}); // End DOMContentLoaded
</script>
</html>