Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>ComfyUI FlowCraft</title> | |
| <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.net.min.js"></script> | |
| <style> | |
| .node { | |
| transition: all 0.3s ease; | |
| } | |
| .node:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2); | |
| } | |
| .connection-path { | |
| stroke-dasharray: 5; | |
| animation: dash 30s linear infinite; | |
| } | |
| @keyframes dash { | |
| to { | |
| stroke-dashoffset: 1000; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-gray-100 min-h-screen"> | |
| <div id="vanta-bg" class="fixed inset-0 -z-10"></div> | |
| <!-- Header --> | |
| <header class="bg-gray-800 bg-opacity-70 backdrop-blur-md border-b border-gray-700"> | |
| <div class="container mx-auto px-4 py-4 flex justify-between items-center"> | |
| <div class="flex items-center space-x-2"> | |
| <i data-feather="cpu" class="text-indigo-400"></i> | |
| <h1 class="text-2xl font-bold bg-gradient-to-r from-indigo-400 to-purple-500 bg-clip-text text-transparent">ComfyUI FlowCraft</h1> | |
| </div> | |
| <nav class="hidden md:flex space-x-6"> | |
| <a href="#" class="hover:text-indigo-400 transition">Dashboard</a> | |
| <a href="#" class="hover:text-indigo-400 transition">Workflows</a> | |
| <a href="#" class="hover:text-indigo-400 transition">API Docs</a> | |
| <a href="#" class="hover:text-indigo-400 transition">Settings</a> | |
| </nav> | |
| <button class="md:hidden"> | |
| <i data-feather="menu"></i> | |
| </button> | |
| <button id="execute-btn" class="hidden md:flex items-center space-x-1 bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 px-4 py-2 rounded-lg transition"> | |
| <i data-feather="play" class="w-4 h-4"></i> | |
| <span>Execute</span> | |
| </button> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="container mx-auto px-4 py-8"> | |
| <div class="grid grid-cols-1 lg:grid-cols-4 gap-6"> | |
| <!-- Workflow Summary --> | |
| <div class="lg:col-span-1 bg-gray-800 bg-opacity-70 rounded-xl p-4 backdrop-blur-md border border-gray-700"> | |
| <h2 class="text-xl font-semibold mb-4 flex items-center"> | |
| <i data-feather="list" class="mr-2 text-indigo-400"></i> | |
| Workflow Summary | |
| </h2> | |
| <div class="space-y-4"> | |
| <div class="bg-gray-700 rounded-lg p-3"> | |
| <h3 class="font-medium text-indigo-300 mb-2">Pose</h3> | |
| <input type="file" id="pose-upload" class="hidden" accept="image/*"> | |
| <label for="pose-upload" class="cursor-pointer text-xs bg-gray-600 hover:bg-gray-500 rounded px-2 py-1 flex items-center justify-center transition"> | |
| <i data-feather="upload" class="mr-1 w-3 h-3"></i> | |
| Upload Image | |
| </label> | |
| <div id="pose-filename" class="text-xs text-gray-400 mt-1 truncate">No file selected</div> | |
| </div> | |
| <div class="bg-gray-700 rounded-lg p-3"> | |
| <h3 class="font-medium text-indigo-300">Prompt Positivo</h3> | |
| <p class="text-xs text-gray-400 text-ellipsis overflow-hidden">This is a professionally staged, high-contrast promotional photograph...</p> | |
| </div> | |
| <div class="bg-gray-700 rounded-lg p-3"> | |
| <h3 class="font-medium text-indigo-300 mb-2">Imagem 2</h3> | |
| <input type="file" id="image2-upload" class="hidden" accept="image/*"> | |
| <label for="image2-upload" class="cursor-pointer text-xs bg-gray-600 hover:bg-gray-500 rounded px-2 py-1 flex items-center justify-center transition"> | |
| <i data-feather="upload" class="mr-1 w-3 h-3"></i> | |
| Upload Image | |
| </label> | |
| <div id="image2-filename" class="text-xs text-gray-400 mt-1 truncate">No file selected</div> | |
| </div> | |
| <div class="bg-gray-700 rounded-lg p-3"> | |
| <h3 class="font-medium text-indigo-300 mb-2">Imagem 3</h3> | |
| <input type="file" id="image3-upload" class="hidden" accept="image/*"> | |
| <label for="image3-upload" class="cursor-pointer text-xs bg-gray-600 hover:bg-gray-500 rounded px-2 py-1 flex items-center justify-center transition"> | |
| <i data-feather="upload" class="mr-1 w-3 h-3"></i> | |
| Upload Image | |
| </label> | |
| <div id="image3-filename" class="text-xs text-gray-400 mt-1 truncate">No file selected</div> | |
| </div> | |
| <div class="bg-gray-700 rounded-lg p-3"> | |
| <h3 class="font-medium text-indigo-300">KSampler</h3> | |
| <div class="grid grid-cols-2 gap-2 text-xs"> | |
| <div> | |
| <span class="text-gray-400">Steps:</span> | |
| <input type="number" class="w-full bg-gray-800 border border-gray-600 rounded p-1 text-white" value="8"> | |
| </div> | |
| <div> | |
| <span class="text-gray-400">Seed:</span> | |
| <input type="number" class="w-full bg-gray-800 border border-gray-600 rounded p-1 text-white" value="573851757936055"> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-700 rounded-lg p-3"> | |
| <h3 class="font-medium text-indigo-300">Tamanho</h3> | |
| <div class="grid grid-cols-2 gap-2 text-xs"> | |
| <div> | |
| <span class="text-gray-400">Width:</span> | |
| <input type="number" class="w-full bg-gray-800 border border-gray-600 rounded p-1 text-white" value="1920"> | |
| </div> | |
| <div> | |
| <span class="text-gray-400">Height:</span> | |
| <input type="number" class="w-full bg-gray-800 border border-gray-600 rounded p-1 text-white" value="1088"> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-700 rounded-lg p-3"> | |
| <h3 class="font-medium text-indigo-300">API Connection</h3> | |
| <div class="space-y-2 text-xs"> | |
| <div> | |
| <span class="text-gray-400">Endpoint:</span> | |
| <input type="text" class="w-full bg-gray-800 border border-gray-600 rounded p-1 text-white" value="https://ia.alemaia.com.br/api" readonly> | |
| </div> | |
| <div> | |
| <span class="text-gray-400">API Key:</span> | |
| <input type="password" class="w-full bg-gray-800 border border-gray-600 rounded p-1 text-white" placeholder="Enter your API key"> | |
| </div> | |
| <button id="test-api" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white rounded px-2 py-1 mt-1 transition flex items-center justify-center"> | |
| <i data-feather="wifi" class="mr-1 w-3 h-3"></i> | |
| Test Connection | |
| </button> | |
| </div> | |
| </div> | |
| <div class="bg-gray-700 rounded-lg p-3"> | |
| <h3 class="font-medium text-indigo-300">Salvar Imagem</h3> | |
| <p class="text-xs text-gray-400">Output will be saved</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Canvas Area --> | |
| <div class="lg:col-span-3 bg-gray-800 bg-opacity-70 rounded-xl p-4 backdrop-blur-md border border-gray-700 min-h-[70vh] relative overflow-hidden"> | |
| <div class="absolute inset-0" id="canvas"> | |
| <!-- Nodes will be placed here --> | |
| <div class="absolute top-20 left-20 w-48 bg-gray-700 rounded-lg shadow-lg p-3 node"> | |
| <div class="flex justify-between items-center mb-2"> | |
| <h3 class="font-medium text-indigo-300">Prompt</h3> | |
| <i data-feather="x" class="text-gray-400 hover:text-red-400 cursor-pointer w-4 h-4"></i> | |
| </div> | |
| <textarea class="w-full bg-gray-800 border border-gray-600 rounded p-2 text-xs text-white" rows="3" placeholder="Enter your prompt..."></textarea> | |
| <div class="connection-point mt-2 w-3 h-3 rounded-full bg-indigo-400 mx-auto"></div> | |
| </div> | |
| <div class="absolute top-20 left-80 w-48 bg-gray-700 rounded-lg shadow-lg p-3 node"> | |
| <div class="flex justify-between items-center mb-2"> | |
| <h3 class="font-medium text-indigo-300">CLIP Encode</h3> | |
| <i data-feather="x" class="text-gray-400 hover:text-red-400 cursor-pointer w-4 h-4"></i> | |
| </div> | |
| <div class="space-y-2"> | |
| <div class="flex justify-between items-center"> | |
| <span class="text-xs text-gray-400">Input</span> | |
| <div class="connection-point w-3 h-3 rounded-full bg-indigo-400"></div> | |
| </div> | |
| <div class="flex justify-between items-center"> | |
| <span class="text-xs text-gray-400">Output</span> | |
| <div class="connection-point w-3 h-3 rounded-full bg-green-400"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <svg class="absolute inset-0 w-full h-full pointer-events-none" id="connections"> | |
| <path d="M134 100 L 234 100" stroke="#818cf8" stroke-width="2" fill="none" class="connection-path" /> | |
| </svg> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <script> | |
| // API Test Connection | |
| document.getElementById('test-api').addEventListener('click', async function() { | |
| const apiKey = document.querySelector('input[type="password"]').value; | |
| if (!apiKey) { | |
| alert('Please enter your API key'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch('https://ia.alemaia.com.br/api/test', { | |
| headers: { | |
| 'Authorization': `Bearer ${apiKey}` | |
| } | |
| }); | |
| if (response.ok) { | |
| alert('API connection successful!'); | |
| } else { | |
| const error = await response.json(); | |
| alert(`API error: ${error.message || 'Unknown error'}`); | |
| } | |
| } catch (err) { | |
| alert('Failed to connect to API: ' + err.message); | |
| } | |
| }); | |
| // Initialize Vanta.js background | |
| VANTA.NET({ | |
| el: "#vanta-bg", | |
| color: 0x4f46e5, | |
| backgroundColor: 0x111827, | |
| points: 10, | |
| maxDistance: 20, | |
| spacing: 15 | |
| }); | |
| // Execute button handler | |
| document.getElementById('execute-btn').addEventListener('click', submitWorkflow); | |
| // Initialize feather icons | |
| feather.replace(); | |
| // File upload handlers | |
| document.getElementById('pose-upload').addEventListener('change', function(e) { | |
| const fileName = e.target.files[0]?.name || 'No file selected'; | |
| document.getElementById('pose-filename').textContent = fileName; | |
| }); | |
| document.getElementById('image2-upload').addEventListener('change', function(e) { | |
| const fileName = e.target.files[0]?.name || 'No file selected'; | |
| document.getElementById('image2-filename').textContent = fileName; | |
| }); | |
| document.getElementById('image3-upload').addEventListener('change', function(e) { | |
| const fileName = e.target.files[0]?.name || 'No file selected'; | |
| document.getElementById('image3-filename').textContent = fileName; | |
| }); | |
| // Submit workflow to API | |
| async function submitWorkflow() { | |
| const apiKey = document.querySelector('input[type="password"]').value; | |
| const workflowData = { | |
| // Collect workflow data from nodes | |
| // This would be expanded with actual workflow data | |
| nodes: [], | |
| connections: [] | |
| }; | |
| try { | |
| const response = await fetch('https://ia.alemaia.com.br/api/execute', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Authorization': `Bearer ${apiKey}` | |
| }, | |
| body: JSON.stringify(workflowData) | |
| }); | |
| const result = await response.json(); | |
| if (response.ok) { | |
| console.log('Workflow executed successfully:', result); | |
| // Handle successful execution | |
| } else { | |
| console.error('Workflow execution failed:', result); | |
| // Handle error | |
| } | |
| } catch (err) { | |
| console.error('Failed to submit workflow:', err); | |
| } | |
| } | |
| // Simple drag functionality for nodes | |
| document.querySelectorAll('.node').forEach(node => { | |
| let isDragging = false; | |
| let offsetX, offsetY; | |
| node.addEventListener('mousedown', (e) => { | |
| if (e.target === node || e.target.parentNode === node) { | |
| isDragging = true; | |
| offsetX = e.clientX - node.getBoundingClientRect().left; | |
| offsetY = e.clientY - node.getBoundingClientRect().top; | |
| node.style.zIndex = 1000; | |
| } | |
| }); | |
| document.addEventListener('mousemove', (e) => { | |
| if (!isDragging) return; | |
| const canvas = document.getElementById('canvas'); | |
| const canvasRect = canvas.getBoundingClientRect(); | |
| let x = e.clientX - canvasRect.left - offsetX; | |
| let y = e.clientY - canvasRect.top - offsetY; | |
| // Constrain to canvas | |
| x = Math.max(0, Math.min(canvasRect.width - node.offsetWidth, x)); | |
| y = Math.max(0, Math.min(canvasRect.height - node.offsetHeight, y)); | |
| node.style.left = `${x}px`; | |
| node.style.top = `${y}px`; | |
| }); | |
| document.addEventListener('mouseup', () => { | |
| isDragging = false; | |
| node.style.zIndex = ''; | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |