Spaces:
Running
Running
| <html> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <title>MCP Blockly</title> | |
| <style> | |
| @keyframes pulseOutline { | |
| 0% { | |
| box-shadow: 0 0 0 0 rgba(255, 255, 255, 1); | |
| } | |
| 75% { | |
| box-shadow: 0 0 0 10px rgba(255, 255, 255, 0); | |
| } | |
| 100% { | |
| box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); | |
| } | |
| } | |
| .flash-button { | |
| animation: pulseOutline 1s ease-out infinite; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="topBar"> | |
| <div id="titleSection"> | |
| <h1>MCP Blockly</h1> | |
| </div> | |
| <div id="divider"></div> | |
| <div id="menuSection"> | |
| <div class="menuGroup"> | |
| <button class="menuButton">File</button> | |
| <div class="dropdown"> | |
| <a href="#" id="newButton" class="dropdownItem" data-action="new">New</a> | |
| <a href="#" id="loadButton" class="dropdownItem" data-action="open">Open</a> | |
| <a href="#" id="saveButton" class="dropdownItem" data-action="download">Download Project</a> | |
| <a href="#" id="downloadCodeButton" class="dropdownItem" data-action="downloadCode">Download Code</a> | |
| <a href="#" id="settingsButton" class="dropdownItem" data-action="downloadCode">API Keys</a> | |
| </div> | |
| </div> | |
| <div class="menuGroup"> | |
| <button class="menuButton">Edit</button> | |
| <div class="dropdown"> | |
| <a href="#" id="undoButton" class="dropdownItem" data-action="undo">Undo</a> | |
| <a href="#" id="redoButton" class="dropdownItem" data-action="redo">Redo</a> | |
| <a href="#" id="cleanWorkspace" class="dropdownItem" data-action="cleanup">Clean up</a> | |
| </div> | |
| </div> | |
| <div class="menuGroup"> | |
| <button class="menuButton">Examples</button> | |
| <div class="dropdown"> | |
| <a href="#" id="weatherButton" class="dropdownItem" data-action="undo">Weather API</a> | |
| <a href="#" id="factButton" class="dropdownItem" data-action="undo">Fact Checker</a> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="githubLink"> | |
| <a href="https://github.com/owenkaplinsky/mcp-blockly" target="_blank" rel="noopener noreferrer"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"> | |
| <path | |
| d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v 3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" /> | |
| </svg> | |
| </a> | |
| </div> | |
| </div> | |
| <div id="pageContainer"> | |
| <div id="outputPane"> | |
| <div id="tabBar"> | |
| <div class="tab active" data-tab="development">Testing</div> | |
| <div class="tab" data-tab="aichat">AI Assistant</div> | |
| </div> | |
| <div id="developmentTab" class="tabContent active"> | |
| <div id="chatContainer"> | |
| <iframe | |
| id="gradioTestFrame" | |
| data-base-src="/gradio-test" | |
| src="about:blank" | |
| style="width: 100%; height: 100%; border: none;" | |
| ></iframe> | |
| </div> | |
| <div class="verticalResizer"></div> | |
| <pre id="generatedCode"><code></code></pre> | |
| </div> | |
| <div id="aichatTab" class="tabContent"> | |
| <div id="gradioContainer"> | |
| <iframe | |
| id="gradioChatFrame" | |
| data-base-src="/gradio-chat" | |
| src="about:blank" | |
| style="width: 100%; height: 100%; border: none;" | |
| ></iframe> | |
| </div> | |
| <pre id="aichatCode" style="position: absolute; left: -9999px; width: 1px; height: 1px;"><code></code></pre> | |
| </div> | |
| </div> | |
| <div class="resizer"></div> | |
| <div id="blocklyDiv"></div> | |
| </div> | |
| <script> | |
| // Tab switching functionality | |
| const tabs = document.querySelectorAll('.tab'); | |
| const tabContents = document.querySelectorAll('.tabContent'); | |
| tabs.forEach(tab => { | |
| tab.addEventListener('click', () => { | |
| const tabName = tab.getAttribute('data-tab'); | |
| // Remove active class from all tabs and contents | |
| tabs.forEach(t => t.classList.remove('active')); | |
| tabContents.forEach(content => content.classList.remove('active')); | |
| // Add active class to clicked tab and corresponding content | |
| tab.classList.add('active'); | |
| document.getElementById(tabName + 'Tab').classList.add('active'); | |
| }); | |
| }); | |
| // Horizontal resizer (output pane vs blockly) | |
| const resizer = document.querySelector('.resizer'); | |
| const outputPane = document.getElementById('outputPane'); | |
| const pageContainer = document.getElementById('pageContainer'); | |
| let startX = 0; | |
| let startWidth = 0; | |
| function onPointerMove(e) { | |
| const containerRect = pageContainer.getBoundingClientRect(); | |
| const containerWidth = containerRect.width; | |
| const dx = e.clientX - startX; | |
| let newWidthPx = startWidth + dx; | |
| const minWidth = containerWidth * 0.2; | |
| const maxWidth = containerWidth * 0.59; | |
| newWidthPx = Math.max(minWidth, Math.min(maxWidth, newWidthPx)); | |
| const newPercent = (newWidthPx / containerWidth) * 100; | |
| outputPane.style.flex = `0 0 ${newPercent}%`; | |
| } | |
| function onPointerUp() { | |
| resizer.releasePointerCapture(activePointerId); | |
| resizer.removeEventListener('pointermove', onPointerMove); | |
| resizer.removeEventListener('pointerup', onPointerUp); | |
| resizer.classList.remove('active'); | |
| document.body.style.cursor = ''; | |
| document.body.style.userSelect = ''; | |
| } | |
| let activePointerId = null; | |
| resizer.addEventListener('pointerdown', (e) => { | |
| const rect = outputPane.getBoundingClientRect(); | |
| startX = e.clientX; | |
| startWidth = rect.width; | |
| activePointerId = e.pointerId; | |
| resizer.classList.add('active'); | |
| document.body.style.cursor = 'col-resize'; | |
| document.body.style.userSelect = 'none'; | |
| resizer.setPointerCapture(activePointerId); | |
| resizer.addEventListener('pointermove', onPointerMove); | |
| resizer.addEventListener('pointerup', onPointerUp); | |
| }); | |
| // Vertical resizer (gradio vs code) | |
| const verticalResizer = document.querySelector('.verticalResizer'); | |
| const chatContainer = document.getElementById('chatContainer'); | |
| const generatedCode = document.getElementById('generatedCode'); | |
| let startY = 0; | |
| let startHeight = 0; | |
| let activePointerId2 = null; | |
| function onVerticalPointerMove(e) { | |
| const outputPaneRect = outputPane.getBoundingClientRect(); | |
| const outputPaneHeight = outputPaneRect.height; | |
| const dy = e.clientY - startY; | |
| let newHeightPx = startHeight + dy; | |
| const minHeight = outputPaneHeight * 0.4; | |
| const maxHeight = outputPaneHeight * 0.78; | |
| newHeightPx = Math.max(minHeight, Math.min(maxHeight, newHeightPx)); | |
| const newPercent = (newHeightPx / outputPaneHeight) * 100; | |
| chatContainer.style.flex = `0 0 ${newPercent}%`; | |
| } | |
| function onVerticalPointerUp() { | |
| verticalResizer.releasePointerCapture(activePointerId2); | |
| verticalResizer.removeEventListener('pointermove', onVerticalPointerMove); | |
| verticalResizer.removeEventListener('pointerup', onVerticalPointerUp); | |
| verticalResizer.classList.remove('active'); | |
| document.body.style.cursor = ''; | |
| document.body.style.userSelect = ''; | |
| } | |
| verticalResizer.addEventListener('pointerdown', (e) => { | |
| const rect = chatContainer.getBoundingClientRect(); | |
| startY = e.clientY; | |
| startHeight = rect.height; | |
| activePointerId2 = e.pointerId; | |
| verticalResizer.classList.add('active'); | |
| document.body.style.cursor = 'row-resize'; | |
| document.body.style.userSelect = 'none'; | |
| verticalResizer.setPointerCapture(activePointerId2); | |
| verticalResizer.addEventListener('pointermove', onVerticalPointerMove); | |
| verticalResizer.addEventListener('pointerup', onVerticalPointerUp); | |
| }); | |
| </script> | |
| <!-- Welcome Modal --> | |
| <div id="welcomeModal" | |
| style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 9999; align-items: center; justify-content: center;"> | |
| <div | |
| style="background: white; padding: 30px; border-radius: 10px; width: 90%; max-width: 700px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); max-height: 90vh; overflow-y: auto; position: relative;"> | |
| <h2 style="margin-top: 0; margin-bottom: 10px; color: #333;">Welcome to MCP Blockly</h2> | |
| <p style="margin: 0 0 20px 0; color: #666; font-size: 14px;">Get started with visual programming for AI tools.</p> | |
| <!-- YouTube Video Embed --> | |
| <div style="margin-bottom: 25px;"> | |
| <iframe width="100%" height="315" src="https://www.youtube.com/embed/5oj-2uIZpb0" | |
| style="border: none; border-radius: 5px;" | |
| allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" | |
| allowfullscreen></iframe> | |
| </div> | |
| <!-- API Keys Section --> | |
| <h3 style="margin-top: 25px; margin-bottom: 15px; color: #333; font-size: 16px; font-weight: 600;">🎉 Free API</h3> | |
| <label for="welcomeApiKeyInput" | |
| style="display: block; margin-bottom: 10px; color: #666; font-size: 14px; font-weight: 500;">OpenAI API | |
| Key (required):</label> | |
| <input type="password" id="welcomeApiKeyInput" | |
| style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; box-sizing: border-box; margin-bottom: 5px;" | |
| placeholder="sk-..."> | |
| <p style="margin: 5px 0 20px 0; color: #999; font-size: 12px;">For AI-powered features and code generation.</p> | |
| <label for="welcomeHfKeyInput" | |
| style="display: block; margin-bottom: 10px; color: #666; font-size: 14px; font-weight: 500;">Hugging Face API | |
| Key (suggested):</label> | |
| <input type="password" id="welcomeHfKeyInput" | |
| style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; box-sizing: border-box; margin-bottom: 5px;" | |
| placeholder="hf_..."> | |
| <p style="margin: 5px 0 20px 0; color: #999; font-size: 12px;">For deploying your MCP server.</p> | |
| <p style="color: #999; font-size: 12px; margin-bottom: 20px;">Your API keys will be stored securely for this session.</p> | |
| <div style="display: flex; justify-content: flex-end; align-items: center; gap: 15px;"> | |
| <div style="display: flex; align-items: center; gap: 6px;"> | |
| <label for="dontShowWelcomeAgain" style="color: #666; font-size: 12px; cursor: pointer; margin: 0;">Don't show me this again</label> | |
| <input type="checkbox" id="dontShowWelcomeAgain" style="cursor: pointer; width: 16px; height: 16px;"> | |
| </div> | |
| <div style="display: flex; gap: 10px;"> | |
| <button id="skipTutorialButton" | |
| style="padding: 10px 20px; background: #e5e7eb; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;">Skip Tutorial</button> | |
| <button id="saveWelcomeApiKey" | |
| style="padding: 10px 20px; background: #6366f1; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;">Start Tutorial</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Keys Modal --> | |
| <div id="apiKeyModal" | |
| style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 9999; align-items: center; justify-content: center;"> | |
| <div | |
| style="background: white; padding: 30px; border-radius: 10px; width: 90%; max-width: 500px; box-shadow: 0 10px 30px rgba(0,0,0,0.2);"> | |
| <h2 style="margin-top: 0; margin-bottom: 20px; color: #333;">API Keys</h2> | |
| <label for="apiKeyInput" | |
| style="display: block; margin-bottom: 10px; color: #666; font-size: 14px; font-weight: 500;">OpenAI API Key (required):</label> | |
| <input type="password" id="apiKeyInput" | |
| style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; box-sizing: border-box; margin-bottom: 5px;" | |
| placeholder="sk-..."> | |
| <p style="margin: 5px 0 20px 0; color: #999; font-size: 12px;">For AI-powered features and code generation.</p> | |
| <label for="hfKeyInput" | |
| style="display: block; margin-bottom: 10px; color: #666; font-size: 14px; font-weight: 500;">Hugging Face API Key (suggested):</label> | |
| <input type="password" id="hfKeyInput" | |
| style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; box-sizing: border-box; margin-bottom: 5px;" | |
| placeholder="hf_..."> | |
| <p style="margin: 5px 0 20px 0; color: #999; font-size: 12px;">For deploying your MCP server.</p> | |
| <p style="color: #999; font-size: 12px;">Your API keys will be stored securely for this session.</p> | |
| <div style="margin-top: 20px; display: flex; justify-content: flex-end; gap: 10px;"> | |
| <button id="cancelApiKey" | |
| style="padding: 10px 20px; background: #e5e7eb; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;">Cancel</button> | |
| <button id="saveApiKey" | |
| style="padding: 10px 20px; background: #6366f1; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;">Save</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Tutorial Popups --> | |
| <div id="tutorialPopup" | |
| style="display: none; position: fixed; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); z-index: 10000; max-width: 280px; border: 2px solid #6366f1; top: 0; left: 0;"> | |
| <h3 id="tutorialTitle" style="margin: 0 0 10px 0; color: #333; font-size: 14px; font-weight: 600;"></h3> | |
| <p id="tutorialBody" style="margin: 0 0 15px 0; color: #666; font-size: 12px; line-height: 1.4;"></p> | |
| <div style="display: flex; justify-content: flex-end;"> | |
| <button id="tutorialSkipButton" | |
| style="padding: 6px 12px; background: #e5e7eb; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">Exit Tutorial</button> | |
| </div> | |
| </div> | |
| </body> | |
| </html> | |