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>