@@ -128,7 +152,8 @@

Device Management Assistant

👋 Welcome to the Device Management Assistant!

-

I can help you manage your connected devices, check their status, and update settings. Try asking me about your devices or specific settings.

+

I can help you manage your connected devices, check their status, and update settings. + Try asking me about your devices or specific settings.

@@ -144,26 +169,27 @@

Device Management Assistant

const chatMessages = document.getElementById('chat-messages'); const userInput = document.getElementById('user-input'); const sendButton = document.getElementById('send-button'); - + // Generate a unique client ID for this session const clientId = Date.now() + Math.random().toString(36).substring(2); - - // WebSocket connection - const ws = new WebSocket(`ws://${window.location.host}/ws/${clientId}`); - + + // WebSocket connection - use secure WebSocket if page is served over HTTPS + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const ws = new WebSocket(`${protocol}//${window.location.host}/ws/${clientId}`); + // Track current response message element for streaming let currentResponseElement = null; let currentResponseContent = ''; - + // Handle WebSocket events ws.onopen = () => { console.log('WebSocket connection established'); }; - + ws.onmessage = (event) => { try { const data = JSON.parse(event.data); - + if (data.error) { // Display error message removeTypingIndicator(); @@ -173,7 +199,7 @@

Device Management Assistant

} else if (data.complete || data.response || data.final_response) { // Response is complete - display the final result removeTypingIndicator(); - + let responseText = ''; if (data.final_response) { responseText = data.final_response; @@ -182,13 +208,13 @@

Device Management Assistant

} else if (currentResponseContent) { responseText = currentResponseContent; } - + if (responseText) { addMessage('assistant', responseText); } else { addMessage('error', 'No response content received'); } - + // Reset streaming state currentResponseElement = null; currentResponseContent = ''; @@ -199,10 +225,10 @@

Device Management Assistant

// Log unexpected data format for debugging console.log('Unexpected message format:', data); } - + // Scroll to bottom chatMessages.scrollTop = chatMessages.scrollHeight; - + } catch (error) { console.error('Error parsing WebSocket message:', error); console.log('Raw message:', event.data); @@ -210,20 +236,20 @@

Device Management Assistant

addMessage('error', 'Error processing server response'); } }; - + ws.onclose = () => { console.log('WebSocket connection closed'); addMessage('system', 'Connection closed. Please refresh the page to reconnect.'); }; - + ws.onerror = (error) => { console.error('WebSocket error:', error); addMessage('error', 'Connection error. Please refresh the page to try again.'); }; - + // Send message when button is clicked sendButton.addEventListener('click', sendMessage); - + // Send message when Enter key is pressed (but allow Shift+Enter for new lines) userInput.addEventListener('keydown', (event) => { if (event.key === 'Enter' && !event.shiftKey) { @@ -231,147 +257,122 @@

Device Management Assistant

sendMessage(); } }); - + // Auto-resize textarea as user types userInput.addEventListener('input', () => { userInput.style.height = 'auto'; userInput.style.height = (userInput.scrollHeight) + 'px'; }); - + function sendMessage() { const message = userInput.value.trim(); if (!message) return; - + // Add user message to chat addMessage('user', message); - + // Add typing indicator addTypingIndicator(); - + // Send message to server ws.send(message); - + // Clear input userInput.value = ''; userInput.style.height = 'auto'; - + // Reset current response tracking currentResponseElement = null; currentResponseContent = ''; - + // Scroll to bottom chatMessages.scrollTop = chatMessages.scrollHeight; } - + function addMessage(role, content) { const messageDiv = document.createElement('div'); messageDiv.className = `message ${role}`; - + const contentDiv = document.createElement('div'); contentDiv.className = 'message-content'; - - // Safe formatting without innerHTML - if (role === 'user') { - contentDiv.textContent = content; - } else { - formatContentSafely(contentDiv, content); - } - + + // Format the content safely + contentDiv.innerHTML = formatContent(content); + messageDiv.appendChild(contentDiv); chatMessages.appendChild(messageDiv); } - - - - function formatContentSafely(container, content) { - const lines = content.split('\n'); - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - - // Handle code blocks - if (line.startsWith('```')) { - const codeBlock = document.createElement('pre'); - const codeElement = document.createElement('code'); - - // Collect code content - let codeContent = ''; - i++; // Skip opening ``` - while (i < lines.length && !lines[i].startsWith('```')) { - codeContent += lines[i] + '\n'; - i++; - } - - codeElement.textContent = codeContent.trim(); - codeBlock.appendChild(codeElement); - container.appendChild(codeBlock); - } else if (line.trim() === '') { - // Empty line - add line break - container.appendChild(document.createElement('br')); - } else { - // Regular line with basic formatting - const p = document.createElement('p'); - p.style.margin = '0.5em 0'; - - // Handle bold text **text** - if (line.includes('**')) { - formatLineWithBold(p, line); + + function formatContent(content) { + // Escape HTML to prevent XSS, then process markdown-like formatting + let formattedContent = content + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(/\*\*(.*?)\*\*/g, '$1') // Bold + .replace(/\*(.*?)\*/g, '$1') // Italic + .replace(/`([^`]+)`/g, '$1') // Inline code + .replace(/\n\n/g, '

') // Double line breaks + .replace(/\n/g, '
'); // Single line breaks + + // Handle code blocks + if (formattedContent.includes('```')) { + const parts = formattedContent.split('```'); + formattedContent = ''; + + for (let i = 0; i < parts.length; i++) { + if (i % 2 === 0) { + // Regular text + formattedContent += parts[i]; } else { - p.textContent = line; + // Code block + let codeContent = parts[i]; + let language = ''; + + // Check if language is specified + if (codeContent.includes('
')) { + const lines = codeContent.split('
'); + language = lines[0].trim(); + codeContent = lines.slice(1).join('
').trim(); + } + + // Add JSON formatting for JSON code blocks + const codeClass = language.toLowerCase() === 'json' ? 'json' : ''; + formattedContent += `
${codeContent}
`; } - - container.appendChild(p); } } - } - - function formatLineWithBold(container, text) { - const parts = text.split('**'); - - for (let i = 0; i < parts.length; i++) { - if (i % 2 === 0) { - // Regular text - if (parts[i]) { - container.appendChild(document.createTextNode(parts[i])); - } - } else { - // Bold text - const bold = document.createElement('strong'); - bold.textContent = parts[i]; - container.appendChild(bold); - } + + // Handle device list formatting with better styling + if (formattedContent.includes('📱') || formattedContent.includes('Device List')) { + formattedContent = formattedContent.replace(/\*\*(\d+\.\s.*?)\*\*/g, '
$1
'); } + + return formattedContent; } - + function addTypingIndicator() { // Remove any existing typing indicator removeTypingIndicator(); - - // Create typing indicator safely + + // Create typing indicator const typingDiv = document.createElement('div'); typingDiv.className = 'message assistant typing-message'; - - const contentDiv = document.createElement('div'); - contentDiv.className = 'message-content'; - - contentDiv.appendChild(document.createTextNode('Thinking')); - - const indicatorDiv = document.createElement('div'); - indicatorDiv.className = 'typing-indicator'; - - for (let i = 0; i < 3; i++) { - const span = document.createElement('span'); - indicatorDiv.appendChild(span); - } - - contentDiv.appendChild(indicatorDiv); - typingDiv.appendChild(contentDiv); + typingDiv.innerHTML = ` +
+ Thinking
+ +
+
+ `; chatMessages.appendChild(typingDiv); - + // Scroll to bottom chatMessages.scrollTop = chatMessages.scrollHeight; } - + function removeTypingIndicator() { const typingIndicator = document.querySelector('.typing-message'); if (typingIndicator) { @@ -380,4 +381,5 @@

Device Management Assistant

} - + + \ No newline at end of file diff --git a/02-use-cases/device-management-agent/frontend/templates/login.html b/02-use-cases/device-management-agent/frontend/templates/login.html index 69d7c1c7..172460b7 100644 --- a/02-use-cases/device-management-agent/frontend/templates/login.html +++ b/02-use-cases/device-management-agent/frontend/templates/login.html @@ -79,7 +79,11 @@

Device Management System

{% endif %} + {% if login_url and (login_url.startswith('https://') or login_url.startswith('http://')) %} + {% else %} + + {% endif %}