516 lines
17 KiB
HTML
516 lines
17 KiB
HTML
<!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>
|