MCP-Blockly / project /src /index.html
owenkaplinsky's picture
Update project/src/index.html
9aca007 verified
<!doctype html>
<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>