comfyui-flowcraft / index.html
asilvamaia's picture
A Api roda em ia.alemaia.com.br
0ecced2 verified
<!DOCTYPE html>
<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>