Skip to main content
Glama

Fusion360MCP

by jaskirat1616
chat_ui.jsβ€’17.4 kB
// Chat UI JavaScript for Fusion 360 MCP let ws = null; let currentConversationId = null; let messageHistory = []; let isWaitingForResponse = false; let sidebarOpen = false; // Initialize WebSocket connection function initWebSocket() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws`; ws = new WebSocket(wsUrl); ws.onopen = () => { updateStatus('Connected', true); console.log('WebSocket connected'); }; ws.onclose = () => { updateStatus('Disconnected', false); console.log('WebSocket disconnected'); // Attempt to reconnect after 3 seconds setTimeout(initWebSocket, 3000); }; ws.onerror = (error) => { console.error('WebSocket error:', error); updateStatus('Error', false); }; ws.onmessage = (event) => { handleWebSocketMessage(JSON.parse(event.data)); }; } // Handle incoming WebSocket messages function handleWebSocketMessage(data) { switch(data.type) { case 'response': addAssistantMessage(data.content, data.code, data.executionResult); isWaitingForResponse = false; removeTypingIndicator(); enableInput(); break; case 'stream': updateStreamingMessage(data.content); break; case 'execution_result': updateExecutionResult(data.messageId, data.result, data.success); break; case 'models': updateModelList(data.models); break; case 'error': showError(data.message); isWaitingForResponse = false; removeTypingIndicator(); enableInput(); break; } } // Update status indicator function updateStatus(text, connected) { const statusText = document.getElementById('statusText'); const statusDot = document.getElementById('statusDot'); statusText.textContent = text; if (connected) { statusDot.classList.remove('error'); } else { statusDot.classList.add('error'); } } // Send message to server function sendMessage() { const input = document.getElementById('messageInput'); const message = input.value.trim(); if (!message || isWaitingForResponse) return; // Hide welcome screen if visible const welcomeScreen = document.getElementById('welcomeScreen'); if (welcomeScreen) { welcomeScreen.style.display = 'none'; } // Add user message to UI addUserMessage(message); // Clear input input.value = ''; input.style.height = 'auto'; // Show typing indicator showTypingIndicator(); // Send via WebSocket if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'message', content: message, conversationId: currentConversationId })); isWaitingForResponse = true; disableInput(); } else { showError('Not connected to server. Please wait...'); removeTypingIndicator(); } } // Add user message to UI function addUserMessage(content) { const container = document.getElementById('messagesContainer'); const messageDiv = document.createElement('div'); messageDiv.className = 'message user'; messageDiv.innerHTML = ` <div class="message-avatar">U</div> <div class="message-content"> <div class="message-text">${escapeHtml(content)}</div> </div> `; container.appendChild(messageDiv); scrollToBottom(); messageHistory.push({ role: 'user', content }); } // Add assistant message to UI function addAssistantMessage(content, code = null, executionResult = null) { const container = document.getElementById('messagesContainer'); const messageDiv = document.createElement('div'); messageDiv.className = 'message assistant'; let messageContent = ` <div class="message-avatar">AI</div> <div class="message-content"> <div class="message-text">${formatMessage(content)}</div> `; if (code) { const codeId = 'code-' + Date.now(); messageContent += ` <div class="code-block"> <div class="code-header"> <span class="code-lang">Python</span> <div class="code-actions"> <button class="code-btn toggle" onclick="toggleCode('${codeId}')"> <span id="${codeId}-toggle-text">Show Code</span> <span id="${codeId}-toggle-icon">β–Ό</span> </button> <button class="code-btn" onclick="copyCode('${codeId}')">Copy</button> <button class="code-btn execute" onclick="executeCode('${codeId}', this)">Execute</button> </div> </div> <div class="code-content" id="${codeId}-content"> <pre id="${codeId}">${escapeHtml(code)}</pre> </div> <div id="${codeId}-result"></div> </div> `; } if (executionResult) { const resultClass = executionResult.success ? 'success' : 'error'; messageContent += ` <div class="execution-result ${resultClass}"> ${executionResult.success ? 'βœ“' : 'βœ—'} ${escapeHtml(executionResult.message)} </div> `; } messageContent += '</div>'; messageDiv.innerHTML = messageContent; container.appendChild(messageDiv); scrollToBottom(); messageHistory.push({ role: 'assistant', content, code, executionResult }); } // Show typing indicator function showTypingIndicator() { const container = document.getElementById('messagesContainer'); const typingDiv = document.createElement('div'); typingDiv.className = 'message assistant typing-indicator-msg'; typingDiv.id = 'typingIndicator'; typingDiv.innerHTML = ` <div class="message-avatar">AI</div> <div class="message-content"> <div class="typing-indicator"> <div class="typing-dot"></div> <div class="typing-dot"></div> <div class="typing-dot"></div> </div> </div> `; container.appendChild(typingDiv); scrollToBottom(); } // Remove typing indicator function removeTypingIndicator() { const indicator = document.getElementById('typingIndicator'); if (indicator) { indicator.remove(); } } // Execute code function executeCode(codeId, button) { const codeElement = document.getElementById(codeId); const code = codeElement.textContent; const resultDiv = document.getElementById(codeId + '-result'); // Disable button button.disabled = true; button.textContent = 'Executing...'; // Send execution request if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'execute', code: code, messageId: codeId })); } // Show loading state resultDiv.innerHTML = '<div class="execution-result">Executing...</div>'; } // Update execution result function updateExecutionResult(messageId, result, success) { const resultDiv = document.getElementById(messageId + '-result'); const button = document.querySelector(`button[onclick*="${messageId}"]`); if (resultDiv) { const resultClass = success ? 'success' : 'error'; resultDiv.innerHTML = ` <div class="execution-result ${resultClass}"> ${success ? 'βœ“ Execution successful' : 'βœ— Execution failed'}: ${escapeHtml(result)} </div> `; } if (button) { button.disabled = false; button.textContent = 'Execute'; } } // Copy code to clipboard function copyCode(codeId) { const codeElement = document.getElementById(codeId); const code = codeElement.textContent; navigator.clipboard.writeText(code).then(() => { // Show temporary success message const button = event.target; const originalText = button.textContent; button.textContent = 'Copied!'; setTimeout(() => { button.textContent = originalText; }, 2000); }); } // Format message (basic markdown support) function formatMessage(text) { // Code blocks text = text.replace(/```(\w+)?\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>'); // Inline code text = text.replace(/`([^`]+)`/g, '<code>$1</code>'); // Bold text = text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>'); // Italic text = text.replace(/\*([^*]+)\*/g, '<em>$1</em>'); // Line breaks text = text.replace(/\n/g, '<br>'); return text; } // Escape HTML function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Scroll to bottom function scrollToBottom() { const container = document.getElementById('messagesContainer'); container.scrollTop = container.scrollHeight; } // Handle Enter key in textarea function handleKeyPress(event) { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); sendMessage(); } } // Auto-resize textarea document.addEventListener('DOMContentLoaded', () => { const textarea = document.getElementById('messageInput'); textarea.addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 200) + 'px'; }); // Initialize WebSocket initWebSocket(); // Load models for current backend loadModels(); }); // Backend selection function updateBackend() { const backend = document.getElementById('backendSelect').value; const apiKeyGroup = document.getElementById('apiKeyGroup'); const modelSelect = document.getElementById('modelSelect'); // Show/hide API key field if (backend === 'ollama') { apiKeyGroup.style.display = 'none'; } else { apiKeyGroup.style.display = 'block'; } // Show loading state modelSelect.innerHTML = '<option value="">Loading models...</option>'; // Load models for selected backend loadModels(); } // Load available models function loadModels() { const backend = document.getElementById('backendSelect').value; const apiKey = document.getElementById('apiKeyInput').value; console.log('Loading models for backend:', backend); // Wait for WebSocket to be ready if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'get_models', backend: backend, apiKey: apiKey || null })); } else { console.error('WebSocket not ready'); setTimeout(loadModels, 1000); // Retry after 1 second } } // Update model list function updateModelList(models) { const select = document.getElementById('modelSelect'); const currentModelSpan = document.getElementById('currentModel'); console.log('Updating model list:', models); select.innerHTML = ''; if (models && models.length > 0) { models.forEach(model => { const option = document.createElement('option'); option.value = model; option.textContent = model; select.appendChild(option); }); const firstModel = models[0]; select.value = firstModel; currentModelSpan.textContent = firstModel; // Send model selection to server if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'set_model', model: firstModel, backend: document.getElementById('backendSelect').value, apiKey: document.getElementById('apiKeyInput').value || null })); } console.log('Model set to:', firstModel); } else { select.innerHTML = '<option value="">No models available</option>'; currentModelSpan.textContent = 'None'; console.warn('No models available for this backend'); } } // Model selection change document.addEventListener('DOMContentLoaded', () => { const modelSelect = document.getElementById('modelSelect'); modelSelect.addEventListener('change', () => { const model = modelSelect.value; document.getElementById('currentModel').textContent = model; if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'set_model', model: model, backend: document.getElementById('backendSelect').value, apiKey: document.getElementById('apiKeyInput').value })); } }); }); // New chat function newChat() { currentConversationId = 'conv-' + Date.now(); messageHistory = []; const container = document.getElementById('messagesContainer'); container.innerHTML = ''; const welcomeScreen = document.createElement('div'); welcomeScreen.className = 'welcome-screen'; welcomeScreen.id = 'welcomeScreen'; welcomeScreen.innerHTML = ` <h2>Welcome to Fusion 360 MCP</h2> <p>AI-powered design automation for Fusion 360. Try one of these examples to get started:</p> <div class="example-prompts"> <div class="example-prompt" onclick="sendExample('Create a 10mm cube at the origin')"> <div class="example-prompt-title">Basic Shape</div> <div class="example-prompt-text">Create a 10mm cube at the origin</div> </div> <div class="example-prompt" onclick="sendExample('Create a cylinder with 20mm diameter and 50mm height')"> <div class="example-prompt-title">Cylinder</div> <div class="example-prompt-text">Create a cylinder with 20mm diameter and 50mm height</div> </div> <div class="example-prompt" onclick="sendExample('Create a rectangular pattern of 5x3 holes, each 3mm diameter, spaced 10mm apart')"> <div class="example-prompt-title">Pattern</div> <div class="example-prompt-text">Create a rectangular pattern of holes</div> </div> <div class="example-prompt" onclick="sendExample('Create a fillet of 2mm radius on all edges of the selected body')"> <div class="example-prompt-title">Fillet</div> <div class="example-prompt-text">Create a fillet on all edges</div> </div> </div> `; container.appendChild(welcomeScreen); } // Send example prompt function sendExample(text) { document.getElementById('messageInput').value = text; sendMessage(); } // Show error message function showError(message) { const container = document.getElementById('messagesContainer'); const errorDiv = document.createElement('div'); errorDiv.className = 'message assistant'; errorDiv.innerHTML = ` <div class="message-avatar">⚠️</div> <div class="message-content"> <div class="execution-result error"> Error: ${escapeHtml(message)} </div> </div> `; container.appendChild(errorDiv); scrollToBottom(); } // Disable input while waiting function disableInput() { document.getElementById('messageInput').disabled = true; document.getElementById('sendBtn').disabled = true; } // Enable input function enableInput() { document.getElementById('messageInput').disabled = false; document.getElementById('sendBtn').disabled = false; document.getElementById('messageInput').focus(); } // Toggle code block visibility function toggleCode(codeId) { const codeContent = document.getElementById(`${codeId}-content`); const toggleText = document.getElementById(`${codeId}-toggle-text`); const toggleIcon = document.getElementById(`${codeId}-toggle-icon`); if (codeContent.classList.contains('expanded')) { codeContent.classList.remove('expanded'); toggleText.textContent = 'Show Code'; toggleIcon.textContent = 'β–Ό'; } else { codeContent.classList.add('expanded'); toggleText.textContent = 'Hide Code'; toggleIcon.textContent = 'β–²'; } } // Toggle sidebar on mobile function toggleSidebar() { const sidebar = document.getElementById('sidebar'); const overlay = document.getElementById('sidebarOverlay'); const hamburger = document.getElementById('hamburgerBtn'); sidebarOpen = !sidebarOpen; if (sidebarOpen) { sidebar.classList.add('open'); overlay.classList.add('show'); hamburger.classList.add('active'); } else { sidebar.classList.remove('open'); overlay.classList.remove('show'); hamburger.classList.remove('active'); } } // Close sidebar function closeSidebar() { const sidebar = document.getElementById('sidebar'); const overlay = document.getElementById('sidebarOverlay'); const hamburger = document.getElementById('hamburgerBtn'); sidebarOpen = false; sidebar.classList.remove('open'); overlay.classList.remove('show'); hamburger.classList.remove('active'); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jaskirat1616/Fusion360MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server