FourLabs-UN2's picture
Faça funcionar a página e simular a contração de um agente
c0ec78e verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AgentFlow Builder - Visual LLM Agent Creator</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://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<style>
.node {
cursor: grab;
transition: all 0.3s ease;
}
.node:active {
cursor: grabbing;
}
.connection {
stroke: #3B82F6;
stroke-width: 2;
fill: none;
marker-end: url(#arrowhead);
}
.connection-path {
pointer-events: none;
}
.tool-node {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.agent-node {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.input-node {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.output-node {
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
.drop-zone {
transition: all 0.3s ease;
}
.drop-zone.drag-over {
background-color: rgba(59, 130, 246, 0.1);
border-color: #3B82F6;
}
.connection-line {
stroke-dasharray: 5;
animation: dash 1s linear infinite;
}
@keyframes dash {
to {
stroke-dashoffset: -10;
}
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<!-- Header -->
<header class="bg-white shadow-sm border-b border-gray-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
<div class="flex items-center space-x-3">
<div class="w-8 h-8 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
<i data-feather="cpu" class="text-white w-4 h-4"></i>
</div>
<h1 class="text-xl font-bold text-gray-900">AgentFlow Builder</h1>
</div>
<div class="flex items-center space-x-4">
<button id="run-agent" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center space-x-2">
<i data-feather="play" class="w-4 h-4"></i>
<span>Run Agent</span>
</button>
<button class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors flex items-center space-x-2">
<i data-feather="save" class="w-4 h-4"></i>
<span>Save</span>
</button>
<button class="p-2 rounded-lg hover:bg-gray-100 transition-colors">
<i data-feather="settings" class="w-5 h-5 text-gray-600"></i>
</button>
</div>
</div>
</div>
</header>
<div class="flex h-[calc(100vh-4rem)]">
<!-- Sidebar -->
<div class="w-80 bg-white border-r border-gray-200 p-6 overflow-y-auto">
<div class="space-y-6">
<!-- Node Categories -->
<div>
<h3 class="text-sm font-semibold text-gray-900 uppercase tracking-wider mb-4">Building Blocks</h3>
<div class="space-y-3">
<!-- Agent Nodes -->
<div class="space-y-2">
<h4 class="text-xs font-medium text-gray-500 uppercase">Agents</h4>
<div class="grid grid-cols-2 gap-2">
<div class="node agent-node p-3 rounded-lg text-white text-center text-sm cursor-grab shadow-md" draggable="true" data-type="agent" data-subtype="llm">
<i data-feather="message-square" class="w-4 h-4 mx-auto mb-1"></i>
<div>LLM Agent</div>
</div>
<div class="node agent-node p-3 rounded-lg text-white text-center text-sm cursor-grab shadow-md" draggable="true" data-type="agent" data-subtype="reasoning">
<i data-feather="brain" class="w-4 h-4 mx-auto mb-1"></i>
<div>Reasoning</div>
</div>
</div>
</div>
<!-- Tools -->
<div class="space-y-2">
<h4 class="text-xs font-medium text-gray-500 uppercase">Tools</h4>
<div class="grid grid-cols-2 gap-2">
<div class="node tool-node p-3 rounded-lg text-white text-center text-sm cursor-grab shadow-md" draggable="true" data-type="tool" data-subtype="web-search">
<i data-feather="search" class="w-4 h-4 mx-auto mb-1"></i>
<div>Web Search</div>
</div>
<div class="node tool-node p-3 rounded-lg text-white text-center text-sm cursor-grab shadow-md" draggable="true" data-type="tool" data-subtype="calculator">
<i data-feather="calculator" class="w-4 h-4 mx-auto mb-1"></i>
<div>Calculator</div>
</div>
<div class="node tool-node p-3 rounded-lg text-white text-center text-sm cursor-grab shadow-md" draggable="true" data-type="tool" data-subtype="file-reader">
<i data-feather="file-text" class="w-4 h-4 mx-auto mb-1"></i>
<div>File Reader</div>
</div>
<div class="node tool-node p-3 rounded-lg text-white text-center text-sm cursor-grab shadow-md" draggable="true" data-type="tool" data-subtype="api-call">
<i data-feather="globe" class="w-4 h-4 mx-auto mb-1"></i>
<div>API Call</div>
</div>
</div>
</div>
<!-- Input/Output -->
<div class="space-y-2">
<h4 class="text-xs font-medium text-gray-500 uppercase">I/O</h4>
<div class="grid grid-cols-2 gap-2">
<div class="node input-node p-3 rounded-lg text-white text-center text-sm cursor-grab shadow-md" draggable="true" data-type="io" data-subtype="input">
<i data-feather="arrow-right" class="w-4 h-4 mx-auto mb-1"></i>
</div>
<div class="node output-node p-3 rounded-lg text-white text-center text-sm cursor-grab shadow-md" draggable="true" data-type="io" data-subtype="output">
<i data-feather="arrow-left" class="w-4 h-4 mx-auto mb-1"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Properties Panel -->
<div class="bg-gray-50 rounded-lg p-4">
<h3 class="text-sm font-semibold text-gray-900 mb-3">Properties</h3>
<div class="space-y-3">
<div>
<label class="text-xs font-medium text-gray-700 block mb-1">Agent Name</label>
<input type="text" class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="My LLM Agent">
</div>
<div>
<label class="text-xs font-medium text-gray-700 block mb-1">Model</label>
<select class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
<option>GPT-4</option>
<option>Claude-3</option>
<option>Gemini Pro</option>
<option>Llama 3</option>
</select>
</div>
<div>
<label class="text-xs font-medium text-gray-700 block mb-1">Temperature</label>
<input type="range" min="0" max="1" step="0.1" value="0.7" class="w-full">
</div>
</div>
</div>
</div>
</div>
<!-- Canvas -->
<div class="flex-1 relative overflow-hidden bg-gradient-to-br from-gray-100 to-gray-200">
<div id="canvas" class="w-full h-full relative drop-zone">
<!-- Connections SVG -->
<svg id="connections" class="absolute top-0 left-0 w-full h-full pointer-events-none">
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#3B82F6"/>
</marker>
</defs>
</svg>
<!-- Default Nodes -->
<div class="node absolute top-20 left-20 bg-white p-4 rounded-xl shadow-lg border-2 border-blue-500" data-node-id="input-1">
<div class="flex items-center space-x-2 mb-2">
<i data-feather="arrow-right" class="w-4 h-4 text-blue-500"></i>
<span class="text-sm font-medium text-gray-900">Input</span>
</div>
<div class="connection-point w-3 h-3 bg-blue-500 rounded-full mx-auto cursor-crosshair" data-node="input-1" data-type="output"></div>
</div>
<div class="node absolute top-20 left-80 bg-white p-4 rounded-xl shadow-lg border-2 border-purple-500" data-node-id="agent-1">
<div class="flex items-center space-x-2 mb-2">
<i data-feather="message-square" class="w-4 h-4 text-purple-500"></i>
<span class="text-sm font-medium text-gray-900">LLM Agent</span>
</div>
<div class="flex justify-between">
<div class="connection-point w-3 h-3 bg-green-500 rounded-full cursor-crosshair" data-node="agent-1" data-type="input"></div>
<div class="connection-point w-3 h-3 bg-blue-500 rounded-full cursor-crosshair" data-node="agent-1" data-type="output"></div>
</div>
</div>
<div class="node absolute top-80 left-80 bg-white p-4 rounded-xl shadow-lg border-2 border-green-500" data-node-id="tool-1">
<div class="flex items-center space-x-2 mb-2">
<i data-feather="search" class="w-4 h-4 text-green-500"></i>
<span class="text-sm font-medium text-gray-900">Web Search</span>
</div>
<div class="flex justify-between">
<div class="connection-point w-3 h-3 bg-green-500 rounded-full cursor-crosshair" data-node="tool-1" data-type="input"></div>
<div class="connection-point w-3 h-3 bg-blue-500 rounded-full cursor-crosshair" data-node="tool-1" data-type="output"></div>
</div>
</div>
<div class="node absolute top-20 left-500 bg-white p-4 rounded-xl shadow-lg border-2 border-green-500" data-node-id="output-1">
<div class="flex items-center space-x-2 mb-2">
<i data-feather="arrow-left" class="w-4 h-4 text-green-500"></i>
<span class="text-sm font-medium text-gray-900">Output</span>
</div>
<div class="connection-point w-3 h-3 bg-green-500 rounded-full mx-auto cursor-crosshair" data-node="output-1" data-type="input"></div>
</div>
</div>
<!-- Connection Line Preview -->
<div id="connection-preview" class="hidden absolute pointer-events-none">
<svg class="w-full h-full">
<line id="preview-line" class="connection-line" stroke="#3B82F6" stroke-width="2" stroke-dasharray="5"/>
</svg>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
feather.replace();
let isDragging = false;
let isConnecting = false;
let startConnectionPoint = null;
let currentDragNode = null;
let offsetX = 0;
let offsetY = 0;
let connections = [];
let isRunning = false;
let currentExecution = null;
// Drag and drop functionality
document.querySelectorAll('.node[draggable="true"]').forEach(node => {
node.addEventListener('dragstart', function(e) {
e.dataTransfer.setData('text/plain', JSON.stringify({
type: this.dataset.type,
subtype: this.dataset.subtype
}));
});
});
const canvas = document.getElementById('canvas');
canvas.addEventListener('dragover', function(e) {
e.preventDefault();
this.classList.add('drag-over');
});
canvas.addEventListener('dragleave', function() {
this.classList.remove('drag-over');
});
canvas.addEventListener('drop', function(e) {
e.preventDefault();
this.classList.remove('drag-over');
const data = JSON.parse(e.dataTransfer.getData('text/plain'));
const rect = this.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
createNode(data.type, data.subtype, x, y);
});
// Node creation
function createNode(type, subtype, x, y) {
const nodeId = `${type}-${Date.now()}`;
let nodeContent = '';
let nodeClass = '';
switch(type) {
case 'agent':
nodeClass = 'agent-node';
nodeContent = `
<div class="flex items-center space-x-2 mb-2">
<i data-feather="message-square" class="w-4 h-4"></i>
<span class="text-sm font-medium">${subtype === 'llm' ? 'LLM Agent' : 'Reasoning Agent'}</span>
</div>
<div class="flex justify-between">
<div class="connection-point w-3 h-3 bg-green-500 rounded-full cursor-crosshair" data-node="${nodeId}" data-type="input"></div>
<div class="connection-point w-3 h-3 bg-blue-500 rounded-full cursor-crosshair" data-node="${nodeId}" data-type="output"></div>
</div>
`;
break;
case 'tool':
nodeClass = 'tool-node';
let icon = 'tool';
let name = 'Tool';
switch(subtype) {
case 'web-search': icon = 'search'; name = 'Web Search'; break;
case 'calculator': icon = 'calculator'; name = 'Calculator'; break;
case 'file-reader': icon = 'file-text'; name = 'File Reader'; break;
case 'api-call': icon = 'globe'; name = 'API Call'; break;
}
nodeContent = `
<div class="flex items-center space-x-2 mb-2">
<i data-feather="${icon}" class="w-4 h-4"></i>
<span class="text-sm font-medium">${name}</span>
</div>
<div class="flex justify-between">
<div class="connection-point w-3 h-3 bg-green-500 rounded-full cursor-crosshair" data-node="${nodeId}" data-type="input"></div>
<div class="connection-point w-3 h-3 bg-blue-500 rounded-full cursor-crosshair" data-node="${nodeId}" data-type="output"></div>
</div>
`;
break;
case 'io':
nodeClass = subtype === 'input' ? 'input-node' : 'output-node';
nodeContent = `
<div class="flex items-center space-x-2 mb-2">
<i data-feather="${subtype === 'input' ? 'arrow-right' : 'arrow-left'}" class="w-4 h-4"></i>
<span class="text-sm font-medium">${subtype === 'input' ? 'Input' : 'Output'}</span>
</div>
<div class="connection-point w-3 h-3 ${subtype === 'input' ? 'bg-blue-500' : 'bg-green-500'} rounded-full mx-auto cursor-crosshair" data-node="${nodeId}" data-type="${subtype === 'input' ? 'output' : 'input'}"></div>
`;
break;
}
const newNode = document.createElement('div');
newNode.className = `node absolute bg-white p-4 rounded-xl shadow-lg border-2 ${nodeClass.includes('node') ? nodeClass : ''}`;
newNode.style.left = `${x - 60}px`;
newNode.style.top = `${y - 30}px`;
newNode.dataset.nodeId = nodeId;
newNode.innerHTML = nodeContent;
canvas.appendChild(newNode);
makeNodeDraggable(newNode);
addConnectionPoints(newNode);
feather.replace();
}
// Make nodes draggable
function makeNodeDraggable(node) {
node.addEventListener('mousedown', function(e) {
if (e.target.classList.contains('connection-point')) return;
isDragging = true;
currentDragNode = node;
const rect = node.getBoundingClientRect();
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
function onMouseMove(e) {
if (!isDragging || !currentDragNode) return;
const canvasRect = canvas.getBoundingClientRect();
const x = e.clientX - canvasRect.left - offsetX;
const y = e.clientY - canvasRect.top - offsetY;
currentDragNode.style.left = `${x}px`;
currentDragNode.style.top = `${y}px`;
updateConnections();
}
function onMouseUp() {
isDragging = false;
currentDragNode = null;
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
}
}
// Connection functionality
function addConnectionPoints(node) {
const points = node.querySelectorAll('.connection-point');
points.forEach(point => {
point.addEventListener('mousedown', startConnection);
});
}
function startConnection(e) {
isConnecting = true;
startConnectionPoint = {
node: e.target.dataset.node,
type: e.target.dataset.type,
element: e.target
};
document.addEventListener('mousemove', drawConnectionPreview);
document.addEventListener('mouseup', endConnection);
}
function drawConnectionPreview(e) {
if (!isConnecting) return;
const canvasRect = canvas.getBoundingClientRect();
const startRect = startConnectionPoint.element.getBoundingClientRect();
const startX = startRect.left - canvasRect.left + startRect.width / 2;
const startY = startRect.top - canvasRect.top + startRect.height / 2;
const endX = e.clientX - canvasRect.left;
const endY = e.clientY - canvasRect.top;
// Show preview line
const preview = document.getElementById('connection-preview');
const line = document.getElementById('preview-line');
preview.classList.remove('hidden');
line.setAttribute('x1', startX);
line.setAttribute('y1', startY);
line.setAttribute('x2', endX);
line.setAttribute('y2', endY);
}
function endConnection(e) {
if (!isConnecting) return;
isConnecting = false;
document.removeEventListener('mousemove', drawConnectionPreview);
document.removeEventListener('mouseup', endConnection);
document.getElementById('connection-preview').classList.add('hidden');
const endElement = document.elementFromPoint(e.clientX, e.clientY);
if (endElement && endElement.classList.contains('connection-point')) {
const endConnectionPoint = {
node: endElement.dataset.node,
type: endElement.dataset.type
};
if (startConnectionPoint.type !== endConnectionPoint.type &&
startConnectionPoint.node !== endConnectionPoint.node) {
createConnection(startConnectionPoint, endConnectionPoint);
}
}
}
function createConnection(start, end) {
const connectionId = `conn-${Date.now()}`;
connections.push({
id: connectionId,
start: start,
end: end
});
updateConnections();
}
function updateConnections() {
const svg = document.getElementById('connections');
svg.innerHTML = '<defs><marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto"><polygon points="0 0, 10 3.5, 0 7" fill="#3B82F6"/></marker></defs>';
connections.forEach(conn => {
const startNode = document.querySelector(`[data-node-id="${conn.start.node}"]`);
const endNode = document.querySelector(`[data-node-id="${conn.end.node}"]`);
if (startNode && endNode) {
const startPoint = startNode.querySelector(`[data-node="${conn.start.node}"][data-type="${conn.start.type}"]`);
const endPoint = endNode.querySelector(`[data-node="${conn.end.node}"][data-type="${conn.end.type}"]`);
if (startPoint && endPoint) {
const startRect = startPoint.getBoundingClientRect();
const endRect = endPoint.getBoundingClientRect();
const canvasRect = canvas.getBoundingClientRect();
const startX = startRect.left - canvasRect.left + startRect.width / 2;
const startY = startRect.top - canvasRect.top + startRect.height / 2;
const endX = endRect.left - canvasRect.left + endRect.width / 2;
const endY = endRect.top - canvasRect.top + endRect.height / 2;
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', `M ${startX} ${startY} L ${endX} ${endY}`);
path.setAttribute('class', 'connection');
path.setAttribute('marker-end', 'url(#arrowhead)');
svg.appendChild(path);
}
});
}
// Initialize existing nodes
document.querySelectorAll('.node[data-node-id]').forEach(node => {
makeNodeDraggable(node);
addConnectionPoints(node);
});
// Add some sample connections
setTimeout(() => {
createConnection(
{ node: 'input-1', type: 'output' },
{ node: 'agent-1', type: 'input' }
);
createConnection(
{ node: 'agent-1', type: 'output' },
{ node: 'output-1', type: 'input' }
);
}, 100);
// Run Agent functionality
document.getElementById('run-agent').addEventListener('click', function() {
if (isRunning) {
stopExecution();
} else {
startExecution();
}
});
function startExecution() {
if (isRunning) return;
isRunning = true;
const runButton = document.getElementById('run-agent');
runButton.innerHTML = '<i data-feather="square" class="w-4 h-4"></i><span>Stop</span>';
feather.replace();
runButton.classList.remove('bg-blue-600', 'hover:bg-blue-700');
runButton.classList.add('bg-red-600', 'hover:bg-red-700');
// Reset all nodes to default state
document.querySelectorAll('.node[data-node-id]').forEach(node => {
node.classList.remove('executing', 'success', 'error');
});
// Simulate agent execution flow
currentExecution = simulateAgentExecution();
}
function stopExecution() {
isRunning = false;
const runButton = document.getElementById('run-agent');
runButton.innerHTML = '<i data-feather="play" class="w-4 h-4"></i><span>Run Agent</span>';
feather.replace();
runButton.classList.remove('bg-red-600', 'hover:bg-red-700');
runButton.classList.add('bg-blue-600', 'hover:bg-blue-700');
if (currentExecution) {
clearTimeout(currentExecution);
currentExecution = null;
}
// Reset all nodes
document.querySelectorAll('.node[data-node-id]').forEach(node => {
node.classList.remove('executing', 'success', 'error');
});
}
function simulateAgentExecution() {
const nodes = {
'input-1': document.querySelector('[data-node-id="input-1"]'),
'agent-1': document.querySelector('[data-node-id="agent-1"]'),
'tool-1': document.querySelector('[data-node-id="tool-1"]'),
'output-1': document.querySelector('[data-node-id="output-1"]')
};
let step = 0;
function executeStep() {
if (!isRunning) return;
switch(step) {
case 0:
// Input node processing
highlightNode(nodes['input-1'], 'executing');
setTimeout(() => {
highlightNode(nodes['input-1'], 'success');
step++;
executeStep();
}, 1000);
break;
case 1:
// LLM Agent processing
highlightNode(nodes['agent-1'], 'executing');
setTimeout(() => {
highlightNode(nodes['agent-1'], 'success');
step++;
executeStep();
}, 2000);
break;
case 2:
// Tool processing (Web Search)
highlightNode(nodes['tool-1'], 'executing');
setTimeout(() => {
highlightNode(nodes['tool-1'], 'success');
step++;
executeStep();
}, 1500);
break;
case 3:
// Output node
highlightNode(nodes['output-1'], 'executing');
setTimeout(() => {
highlightNode(nodes['output-1'], 'success');
// Execution complete
setTimeout(() => {
stopExecution();
showExecutionResult();
}, 500);
break;
}
}
executeStep();
return currentExecution;
}
function highlightNode(node, state) {
node.classList.remove('executing', 'success', 'error');
if (state) {
node.classList.add(state);
// Add pulsing animation for executing state
if (state === 'executing') {
node.style.animation = 'pulse 1.5s infinite';
} else {
node.style.animation = '';
}
}
function showExecutionResult() {
// Create a simple result modal
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
modal.innerHTML = `
<div class="bg-white rounded-xl p-6 max-w-md w-full mx-4">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900">Execution Complete</h3>
<button onclick="this.parentElement.parentElement.remove()" class="text-gray-400 hover:text-gray-600">
<i data-feather="x" class="w-5 h-5"></i>
</button>
</div>
<div class="space-y-3">
<div class="flex items-center space-x-2 text-green-600">
<i data-feather="check-circle" class="w-5 h-5"></i>
<span class="text-sm font-medium">Agent executed successfully!</span>
</div>
<div class="bg-gray-50 rounded-lg p-4">
<h4 class="text-xs font-medium text-gray-700 mb-2">Execution Log</h4>
<div class="text-xs text-gray-600 space-y-1">
<div>✓ Input processed</div>
<div>✓ LLM reasoning completed</div>
<div>✓ Web search executed</div>
<div>✓ Output generated</div>
</div>
</div>
<div class="mt-6 flex justify-end">
<button onclick="this.parentElement.parentElement.parentElement.remove()" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
Close
</button>
</div>
`;
document.body.appendChild(modal);
feather.replace();
}
// Add CSS for execution states
const style = document.createElement('style');
style.textContent = `
.node.executing {
border-color: #3B82F6 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
}
.node.success {
border-color: #10B981 !important;
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.3);
}
.node.error {
border-color: #EF4444 !important;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.3);
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); }
100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); }
}
`;
document.head.appendChild(style);
});
</script>
</body>
</html>