| | <!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 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)]"> |
| | |
| | <div class="w-80 bg-white border-r border-gray-200 p-6 overflow-y-auto"> |
| | <div class="space-y-6"> |
| | |
| | <div> |
| | <h3 class="text-sm font-semibold text-gray-900 uppercase tracking-wider mb-4">Building Blocks</h3> |
| | <div class="space-y-3"> |
| | |
| | <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> |
| |
|
| | |
| | <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> |
| |
|
| | |
| | <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> |
| |
|
| | |
| | <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> |
| |
|
| | |
| | <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"> |
| | |
| | <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> |
| |
|
| | |
| | <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> |
| |
|
| | |
| | <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; |
| | |
| | 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); |
| | }); |
| | |
| | |
| | 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(); |
| | } |
| | |
| | |
| | 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); |
| | } |
| | } |
| | |
| | |
| | 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; |
| | |
| | |
| | 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); |
| | } |
| | }); |
| | } |
| | |
| | |
| | document.querySelectorAll('.node[data-node-id]').forEach(node => { |
| | makeNodeDraggable(node); |
| | addConnectionPoints(node); |
| | }); |
| | |
| | setTimeout(() => { |
| | createConnection( |
| | { node: 'input-1', type: 'output' }, |
| | { node: 'agent-1', type: 'input' } |
| | ); |
| | createConnection( |
| | { node: 'agent-1', type: 'output' }, |
| | { node: 'output-1', type: 'input' } |
| | ); |
| | }, 100); |
| | |
| | |
| | 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'); |
| | |
| | |
| | document.querySelectorAll('.node[data-node-id]').forEach(node => { |
| | node.classList.remove('executing', 'success', 'error'); |
| | }); |
| | |
| | |
| | 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; |
| | } |
| | |
| | |
| | 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: |
| | |
| | highlightNode(nodes['input-1'], 'executing'); |
| | setTimeout(() => { |
| | highlightNode(nodes['input-1'], 'success'); |
| | step++; |
| | executeStep(); |
| | }, 1000); |
| | break; |
| | |
| | case 1: |
| | |
| | highlightNode(nodes['agent-1'], 'executing'); |
| | setTimeout(() => { |
| | highlightNode(nodes['agent-1'], 'success'); |
| | step++; |
| | executeStep(); |
| | }, 2000); |
| | break; |
| | |
| | case 2: |
| | |
| | highlightNode(nodes['tool-1'], 'executing'); |
| | setTimeout(() => { |
| | highlightNode(nodes['tool-1'], 'success'); |
| | step++; |
| | executeStep(); |
| | }, 1500); |
| | break; |
| | |
| | case 3: |
| | |
| | highlightNode(nodes['output-1'], 'executing'); |
| | setTimeout(() => { |
| | highlightNode(nodes['output-1'], 'success'); |
| | |
| | setTimeout(() => { |
| | stopExecution(); |
| | showExecutionResult(); |
| | }, 500); |
| | break; |
| | } |
| | } |
| | |
| | executeStep(); |
| | return currentExecution; |
| | } |
| | |
| | function highlightNode(node, state) { |
| | node.classList.remove('executing', 'success', 'error'); |
| | if (state) { |
| | node.classList.add(state); |
| | |
| | |
| | if (state === 'executing') { |
| | node.style.animation = 'pulse 1.5s infinite'; |
| | } else { |
| | node.style.animation = ''; |
| | } |
| | } |
| | |
| | function showExecutionResult() { |
| | |
| | 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(); |
| | } |
| | |
| | |
| | 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> |
| |
|