Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Insur.MCP | Agent Workflow Canvas</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| /* Custom styles for canvas elements */ | |
| .canvas-area { | |
| background-color: #f8fafc; | |
| border: 2px dashed #cbd5e1; | |
| border-radius: 0.5rem; | |
| min-height: 500px; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .draggable-item { | |
| cursor: grab; | |
| user-select: none; | |
| transition: all 0.2s ease; | |
| } | |
| .draggable-item:active { | |
| cursor: grabbing; | |
| } | |
| .canvas-node { | |
| position: absolute; | |
| background: white; | |
| border-radius: 0.5rem; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
| min-width: 180px; | |
| z-index: 10; | |
| } | |
| .canvas-node:hover { | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); | |
| } | |
| .node-header { | |
| padding: 0.5rem 1rem; | |
| border-bottom: 1px solid #e2e8f0; | |
| font-weight: 600; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .node-content { | |
| padding: 1rem; | |
| } | |
| .node-connector { | |
| width: 12px; | |
| height: 12px; | |
| border-radius: 50%; | |
| background: #94a3b8; | |
| position: absolute; | |
| cursor: pointer; | |
| } | |
| .node-connector.input { | |
| left: -6px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| } | |
| .node-connector.output { | |
| right: -6px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| } | |
| .connection-line { | |
| position: absolute; | |
| height: 2px; | |
| background: #64748b; | |
| z-index: 5; | |
| pointer-events: none; | |
| } | |
| .tab-content { | |
| display: none; | |
| } | |
| .tab-content.active { | |
| display: block; | |
| } | |
| .tab-button.active { | |
| background-color: #e2e8f0; | |
| font-weight: 600; | |
| } | |
| /* Animation for empty canvas */ | |
| @keyframes pulse { | |
| 0%, 100% { | |
| opacity: 0.5; | |
| } | |
| 50% { | |
| opacity: 0.2; | |
| } | |
| } | |
| .empty-canvas { | |
| animation: pulse 2s infinite; | |
| } | |
| </style> | |
| </head> | |
| <body class="font-sans antialiased text-gray-800 bg-white"> | |
| <!-- Navigation --> | |
| <nav class="bg-white shadow-sm sticky top-0 z-50"> | |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | |
| <div class="flex justify-between h-16"> | |
| <div class="flex items-center"> | |
| <div class="flex-shrink-0 flex items-center"> | |
| <a href="index.html" class="text-blue-600 font-bold text-xl">Insur.MCP</a> | |
| </div> | |
| </div> | |
| <div class="hidden md:flex items-center space-x-8"> | |
| <a href="index.html" class="text-gray-500 hover:text-blue-600 font-medium px-1">Home</a> | |
| <a href="index.html#features" class="text-gray-500 hover:text-blue-600 font-medium px-1">Features</a> | |
| <a href="index.html#about" class="text-gray-500 hover:text-blue-600 font-medium px-1">About</a> | |
| <a href="#" class="text-blue-600 font-medium border-b-2 border-blue-600 px-1">Canvas</a> | |
| <button class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md font-medium transition duration-300"> | |
| Contact Us | |
| </button> | |
| </div> | |
| <div class="md:hidden flex items-center"> | |
| <button id="mobile-menu-button" class="text-gray-500 hover:text-blue-600 focus:outline-none"> | |
| <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="mobile-menu" class="mobile-menu md:hidden bg-white"> | |
| <div class="px-2 pt-2 pb-3 space-y-1 sm:px-3"> | |
| <a href="index.html" class="block px-3 py-2 rounded-md text-base font-medium text-gray-500 hover:text-blue-600 hover:bg-blue-50">Home</a> | |
| <a href="index.html#features" class="block px-3 py-2 rounded-md text-base font-medium text-gray-500 hover:text-blue-600 hover:bg-blue-50">Features</a> | |
| <a href="index.html#about" class="block px-3 py-2 rounded-md text-base font-medium text-gray-500 hover:text-blue-600 hover:bg-blue-50">About</a> | |
| <a href="#" class="block px-3 py-2 rounded-md text-base font-medium text-blue-600 bg-blue-50">Canvas</a> | |
| <button class="w-full text-left block px-3 py-2 rounded-md text-base font-medium text-white bg-blue-600 hover:bg-blue-700"> | |
| Contact Us | |
| </button> | |
| </div> | |
| </div> | |
| </nav> | |
| <!-- Canvas Subpage Content --> | |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> | |
| <div class="flex justify-between items-center mb-8"> | |
| <div> | |
| <h1 class="text-3xl font-bold text-gray-900">Agent Workflow Canvas</h1> | |
| <p class="text-gray-600 mt-2">Drag and drop components to create your AI agent workflow</p> | |
| </div> | |
| <div class="flex space-x-3"> | |
| <button id="clear-canvas" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"> | |
| Clear Canvas | |
| </button> | |
| <button id="save-workflow" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"> | |
| Save Workflow | |
| </button> | |
| </div> | |
| </div> | |
| <div class="flex flex-col lg:flex-row gap-6"> | |
| <!-- Components Panel --> | |
| <div class="lg:w-1/4 bg-gray-50 p-4 rounded-lg"> | |
| <div class="flex border-b border-gray-200 mb-4"> | |
| <button class="tab-button active px-4 py-2 rounded-t-lg" data-tab="agents">Agents</button> | |
| <button class="tab-button px-4 py-2 rounded-t-lg" data-tab="tasks">Tasks</button> | |
| <button class="tab-button px-4 py-2 rounded-t-lg" data-tab="tools">Tools</button> | |
| <button class="tab-button px-4 py-2 rounded-t-lg" data-tab="data">Data</button> | |
| </div> | |
| <!-- Agents Tab --> | |
| <div id="agents" class="tab-content active"> | |
| <div class="space-y-3"> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="agent" data-subtype="underwriting_agent"> | |
| <i class="fas fa-user-tie text-purple-500 mr-2"></i> Underwriting Agent | |
| </div> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="agent" data-subtype="sales_agent"> | |
| <i class="fas fa-handshake text-purple-500 mr-2"></i> Sales Agent | |
| </div> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="agent" data-subtype="fraud_agent"> | |
| <i class="fas fa-search-dollar text-purple-500 mr-2"></i> Fraud Agent | |
| </div> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="agent" data-subtype="claims_agent"> | |
| <i class="fas fa-file-invoice-dollar text-purple-500 mr-2"></i> Claims Agent | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Tasks Tab --> | |
| <div id="tasks" class="tab-content"> | |
| <div class="space-y-3"> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="task" data-subtype="risk_assessment"> | |
| <i class="fas fa-shield-alt text-blue-500 mr-2"></i> Risk Assessment | |
| </div> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="task" data-subtype="fraud_detection"> | |
| <i class="fas fa-search-dollar text-blue-500 mr-2"></i> Fraud Detection | |
| </div> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="task" data-subtype="sales_prospective"> | |
| <i class="fas fa-bullseye text-blue-500 mr-2"></i> Sales Prospective | |
| </div> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="task" data-subtype="appraisal"> | |
| <i class="fas fa-clipboard-check text-blue-500 mr-2"></i> Appraisal | |
| </div> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="task" data-subtype="estimation"> | |
| <i class="fas fa-calculator text-blue-500 mr-2"></i> Estimation | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Tools Tab --> | |
| <div id="tools" class="tab-content"> | |
| <div class="space-y-3"> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="tool" data-subtype="web_search"> | |
| <i class="fas fa-globe text-green-500 mr-2"></i> Web Search | |
| </div> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="tool" data-subtype="data_set"> | |
| <i class="fas fa-database text-green-500 mr-2"></i> Data Set | |
| </div> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="tool" data-subtype="analysis"> | |
| <i class="fas fa-chart-line text-green-500 mr-2"></i> Analysis | |
| </div> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="tool" data-subtype="delegation"> | |
| <i class="fas fa-users text-green-500 mr-2"></i> Delegation | |
| </div> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="tool" data-subtype="think"> | |
| <i class="fas fa-brain text-green-500 mr-2"></i> Think | |
| </div> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="tool" data-subtype="output_delivery"> | |
| <i class="fas fa-paper-plane text-green-500 mr-2"></i> Output Delivery | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Data Tab --> | |
| <div id="data" class="tab-content"> | |
| <div class="space-y-3"> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="data" data-subtype="customer_synt"> | |
| <i class="fas fa-users text-orange-500 mr-2"></i> Customer Synthetic | |
| </div> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="data" data-subtype="claim_synt"> | |
| <i class="fas fa-file-invoice text-orange-500 mr-2"></i> Claim Synthetic | |
| </div> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="data" data-subtype="process_synt"> | |
| <i class="fas fa-project-diagram text-orange-500 mr-2"></i> Process Synthetic | |
| </div> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="data" data-subtype="dataset"> | |
| <i class="fas fa-table text-orange-500 mr-2"></i> Dataset | |
| </div> | |
| <div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move" draggable="true" data-type="data" data-subtype="sales"> | |
| <i class="fas fa-chart-bar text-orange-500 mr-2"></i> Sales Data | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Canvas Area --> | |
| <div class="lg:w-3/4"> | |
| <div id="workflow-canvas" class="canvas-area p-6"> | |
| <div class="empty-canvas text-center py-20 text-gray-400"> | |
| <i class="fas fa-arrows-alt text-4xl mb-4"></i> | |
| <p class="text-xl">Drag components here to build your workflow</p> | |
| <p class="text-sm mt-2">Connect nodes by dragging from output to input connectors</p> | |
| </div> | |
| </div> | |
| <div class="mt-4 flex justify-between items-center"> | |
| <div class="text-sm text-gray-500"> | |
| <span id="node-count">0</span> nodes on canvas | |
| </div> | |
| <div class="flex space-x-3"> | |
| <button id="zoom-in" class="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded"> | |
| <i class="fas fa-search-plus"></i> | |
| </button> | |
| <button id="zoom-out" class="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded"> | |
| <i class="fas fa-search-minus"></i> | |
| </button> | |
| <button id="center-canvas" class="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded"> | |
| <i class="fas fa-expand"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Mobile menu toggle | |
| const mobileMenuButton = document.getElementById('mobile-menu-button'); | |
| const mobileMenu = document.getElementById('mobile-menu'); | |
| mobileMenuButton.addEventListener('click', () => { | |
| mobileMenu.classList.toggle('open'); | |
| }); | |
| // Tab functionality | |
| const tabButtons = document.querySelectorAll('.tab-button'); | |
| const tabContents = document.querySelectorAll('.tab-content'); | |
| tabButtons.forEach(button => { | |
| button.addEventListener('click', () => { | |
| // Remove active class from all buttons and contents | |
| tabButtons.forEach(btn => btn.classList.remove('active')); | |
| tabContents.forEach(content => content.classList.remove('active')); | |
| // Add active class to clicked button and corresponding content | |
| button.classList.add('active'); | |
| const tabId = button.getAttribute('data-tab'); | |
| document.getElementById(tabId).classList.add('active'); | |
| }); | |
| }); | |
| // Canvas functionality | |
| const canvas = document.getElementById('workflow-canvas'); | |
| const draggableItems = document.querySelectorAll('.draggable-item'); | |
| const clearCanvasBtn = document.getElementById('clear-canvas'); | |
| const saveWorkflowBtn = document.getElementById('save-workflow'); | |
| const nodeCountElement = document.getElementById('node-count'); | |
| const emptyCanvasElement = document.querySelector('.empty-canvas'); | |
| let nodes = []; | |
| let connections = []; | |
| let isDragging = false; | |
| let startConnector = null; | |
| let tempLine = null; | |
| let canvasScale = 1; | |
| // Update node count | |
| function updateNodeCount() { | |
| const count = document.querySelectorAll('.canvas-node').length; | |
| nodeCountElement.textContent = count; | |
| if (count > 0) { | |
| emptyCanvasElement.style.display = 'none'; | |
| } else { | |
| emptyCanvasElement.style.display = 'block'; | |
| } | |
| } | |
| // Create a new node on canvas | |
| function createNode(type, subtype, x, y) { | |
| const nodeId = 'node-' + Date.now(); | |
| const node = document.createElement('div'); | |
| node.className = 'canvas-node'; | |
| node.id = nodeId; | |
| node.style.left = `${x}px`; | |
| node.style.top = `${y}px`; | |
| // Set node color based on type | |
| let icon, color, title; | |
| switch(type) { | |
| case 'agent': | |
| icon = 'robot'; | |
| color = 'purple'; | |
| title = 'Agent: ' + subtype.replace('_', ' '); | |
| break; | |
| case 'task': | |
| icon = 'tasks'; | |
| color = 'blue'; | |
| title = 'Task: ' + subtype.replace('_', ' '); | |
| break; | |
| case 'tool': | |
| icon = 'tools'; | |
| color = 'green'; | |
| title = 'Tool: ' + subtype.replace('_', ' '); | |
| break; | |
| case 'data': | |
| icon = 'database'; | |
| color = 'orange'; | |
| title = 'Data: ' + subtype.replace('_', ' '); | |
| break; | |
| } | |
| // Create node HTML | |
| node.innerHTML = ` | |
| <div class="node-header bg-${color}-50 text-${color}-700"> | |
| <div> | |
| <i class="fas fa-${icon} mr-2"></i> | |
| ${title} | |
| </div> | |
| <button class="node-delete text-gray-400 hover:text-red-500"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="node-content"> | |
| <select class="w-full p-2 border border-gray-300 rounded text-sm"> | |
| ${getOptionsForType(type)} | |
| </select> | |
| <div class="mt-2 text-xs text-gray-500"> | |
| <i class="fas fa-info-circle mr-1"></i> | |
| ${getDescriptionForSubtype(type, subtype)} | |
| </div> | |
| </div> | |
| <div class="node-connector input" data-node="${nodeId}" data-type="input"></div> | |
| <div class="node-connector output" data-node="${nodeId}" data-type="output"></div> | |
| `; | |
| // Add node to canvas | |
| canvas.appendChild(node); | |
| // Make node draggable | |
| makeNodeDraggable(node); | |
| // Add delete functionality | |
| const deleteBtn = node.querySelector('.node-delete'); | |
| deleteBtn.addEventListener('click', () => { | |
| // Remove any connections to this node | |
| connections = connections.filter(conn => { | |
| return conn.fromNode !== nodeId && conn.toNode !== nodeId; | |
| }); | |
| // Remove connection lines from DOM | |
| document.querySelectorAll('.connection-line').forEach(line => { | |
| if (line.dataset.fromNode === nodeId || line.dataset.toNode === nodeId) { | |
| line.remove(); | |
| } | |
| }); | |
| // Remove the node | |
| node.remove(); | |
| updateNodeCount(); | |
| }); | |
| // Store node in array | |
| nodes.push({ | |
| id: nodeId, | |
| type: type, | |
| subtype: subtype, | |
| element: node | |
| }); | |
| updateNodeCount(); | |
| return node; | |
| } | |
| // Get options for select dropdown based on node type | |
| function getOptionsForType(type) { | |
| let options = ''; | |
| switch(type) { | |
| case 'agent': | |
| options = ` | |
| <option value="underwriting_agent">Underwriting Agent</option> | |
| <option value="sales_agent">Sales Agent</option> | |
| <option value="fraud_agent">Fraud Agent</option> | |
| <option value="claims_agent">Claims Agent</option> | |
| `; | |
| break; | |
| case 'task': | |
| options = ` | |
| <option value="risk_assessment">Risk Assessment</option> | |
| <option value="fraud_detection">Fraud Detection</option> | |
| <option value="sales_prospective">Sales Prospective</option> | |
| <option value="appraisal">Appraisal</option> | |
| <option value="estimation">Estimation</option> | |
| `; | |
| break; | |
| case 'tool': | |
| options = ` | |
| <option value="web_search">Web Search</option> | |
| <option value="data_set">Data Set</option> | |
| <option value="analysis">Analysis</option> | |
| <option value="delegation">Delegation</option> | |
| <option value="think">Think</option> | |
| <option value="output_delivery">Output Delivery</option> | |
| `; | |
| break; | |
| case 'data': | |
| options = ` | |
| <option value="customer_synt">Customer Synthetic</option> | |
| <option value="claim_synt">Claim Synthetic</option> | |
| <option value="process_synt">Process Synthetic</option> | |
| <option value="dataset">Dataset</option> | |
| <option value="sales">Sales Data</option> | |
| `; | |
| break; | |
| } | |
| return options; | |
| } | |
| // Get description for subtype | |
| function getDescriptionForSubtype(type, subtype) { | |
| const descriptions = { | |
| agent: { | |
| underwriting_agent: "Evaluates and assesses risks for insurance policies", | |
| sales_agent: "Handles customer acquisition and policy sales", | |
| fraud_agent: "Detects and investigates potential fraudulent claims", | |
| claims_agent: "Manages and processes insurance claims" | |
| }, | |
| task: { | |
| risk_assessment: "Analyzes and evaluates potential risks", | |
| fraud_detection: "Identifies suspicious patterns in claims", | |
| sales_prospective: "Finds and qualifies potential customers", | |
| appraisal: "Evaluates property or damage for claims", | |
| estimation: "Calculates claim amounts or policy costs" | |
| }, | |
| tool: { | |
| web_search: "Searches the web for relevant information", | |
| data_set: "Accesses structured data for analysis", | |
| analysis: "Performs data analysis and interpretation", | |
| delegation: "Assigns tasks to other agents", | |
| think: "Processes information and makes decisions", | |
| output_delivery: "Sends results to the next step" | |
| }, | |
| data: { | |
| customer_synt: "Synthetic customer profile data", | |
| claim_synt: "Synthetic insurance claim data", | |
| process_synt: "Synthetic process workflow data", | |
| dataset: "General structured dataset", | |
| sales: "Historical sales performance data" | |
| } | |
| }; | |
| return descriptions[type]?.[subtype] || "No description available"; | |
| } | |
| // Make node draggable | |
| function makeNodeDraggable(node) { | |
| const header = node.querySelector('.node-header'); | |
| header.addEventListener('mousedown', (e) => { | |
| if (e.target.classList.contains('node-delete') || e.target.closest('.node-delete')) { | |
| return; // Don't drag if clicking delete button | |
| } | |
| const startX = e.clientX; | |
| const startY = e.clientY; | |
| const startLeft = parseInt(node.style.left); | |
| const startTop = parseInt(node.style.top); | |
| function moveNode(e) { | |
| const dx = e.clientX - startX; | |
| const dy = e.clientY - startY; | |
| node.style.left = `${startLeft + dx}px`; | |
| node.style.top = `${startTop + dy}px`; | |
| // Update connection lines | |
| updateConnectionLines(); | |
| } | |
| function stopDrag() { | |
| document.removeEventListener('mousemove', moveNode); | |
| document.removeEventListener('mouseup', stopDrag); | |
| } | |
| document.addEventListener('mousemove', moveNode); | |
| document.addEventListener('mouseup', stopDrag); | |
| }); | |
| } | |
| // Create a connection between two nodes | |
| function createConnection(fromNodeId, toNodeId) { | |
| // Check if connection already exists | |
| const existingConnection = connections.find(conn => | |
| conn.fromNode === fromNodeId && conn.toNode === toNodeId | |
| ); | |
| if (existingConnection) return; | |
| // Add to connections array | |
| connections.push({ | |
| fromNode: fromNodeId, | |
| toNode: toNodeId | |
| }); | |
| // Create connection line | |
| updateConnectionLines(); | |
| } | |
| // Update all connection lines | |
| function updateConnectionLines() { | |
| // Remove all existing connection lines | |
| document.querySelectorAll('.connection-line').forEach(line => line.remove()); | |
| // Create new connection lines | |
| connections.forEach(conn => { | |
| const fromNode = document.getElementById(conn.fromNode); | |
| const toNode = document.getElementById(conn.toNode); | |
| if (fromNode && toNode) { | |
| const fromConnector = fromNode.querySelector('.node-connector.output'); | |
| const toConnector = toNode.querySelector('.node-connector.input'); | |
| const fromRect = fromConnector.getBoundingClientRect(); | |
| const toRect = toConnector.getBoundingClientRect(); | |
| const canvasRect = canvas.getBoundingClientRect(); | |
| const fromX = fromRect.left + fromRect.width / 2 - canvasRect.left; | |
| const fromY = fromRect.top + fromRect.height / 2 - canvasRect.top; | |
| const toX = toRect.left + toRect.width / 2 - canvasRect.left; | |
| const toY = toRect.top + toRect.height / 2 - canvasRect.top; | |
| const length = Math.sqrt(Math.pow(toX - fromX, 2) + Math.pow(toY - fromY, 2)); | |
| const angle = Math.atan2(toY - fromY, toX - fromX) * 180 / Math.PI; | |
| const line = document.createElement('div'); | |
| line.className = 'connection-line'; | |
| line.style.width = `${length}px`; | |
| line.style.left = `${fromX}px`; | |
| line.style.top = `${fromY}px`; | |
| line.style.transform = `rotate(${angle}deg)`; | |
| line.style.transformOrigin = '0 0'; | |
| line.dataset.fromNode = conn.fromNode; | |
| line.dataset.toNode = conn.toNode; | |
| canvas.appendChild(line); | |
| } | |
| }); | |
| } | |
| // Handle drag and drop from components panel to canvas | |
| draggableItems.forEach(item => { | |
| item.addEventListener('dragstart', (e) => { | |
| e.dataTransfer.setData('type', e.target.dataset.type); | |
| e.dataTransfer.setData('subtype', e.target.dataset.subtype); | |
| e.dataTransfer.effectAllowed = 'copy'; | |
| }); | |
| }); | |
| canvas.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| e.dataTransfer.dropEffect = 'copy'; | |
| }); | |
| canvas.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| const type = e.dataTransfer.getData('type'); | |
| const subtype = e.dataTransfer.getData('subtype'); | |
| if (!type || !subtype) return; | |
| const rect = canvas.getBoundingClientRect(); | |
| const x = e.clientX - rect.left - 90; // Center the node on cursor | |
| const y = e.clientY - rect.top - 40; | |
| createNode(type, subtype, x, y); | |
| }); | |
| // Handle connector dragging for creating connections | |
| canvas.addEventListener('mousedown', (e) => { | |
| const connector = e.target.closest('.node-connector'); | |
| if (!connector) return; | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| startConnector = { | |
| node: connector.dataset.node, | |
| type: connector.dataset.type, | |
| x: e.clientX, | |
| y: e.clientY | |
| }; | |
| // Create temporary line | |
| tempLine = document.createElement('div'); | |
| tempLine.className = 'connection-line'; | |
| tempLine.style.backgroundColor = '#6366f1'; | |
| canvas.appendChild(tempLine); | |
| isDragging = true; | |
| }); | |
| document.addEventListener('mousemove', (e) => { | |
| if (!isDragging || !startConnector) return; | |
| const fromNode = document.getElementById(startConnector.node); | |
| if (!fromNode) return; | |
| const fromConnector = fromNode.querySelector(`.node-connector.${startConnector.type}`); | |
| const fromRect = fromConnector.getBoundingClientRect(); | |
| const canvasRect = canvas.getBoundingClientRect(); | |
| const fromX = fromRect.left + fromRect.width / 2 - canvasRect.left; | |
| const fromY = fromRect.top + fromRect.height / 2 - canvasRect.top; | |
| const toX = e.clientX - canvasRect.left; | |
| const toY = e.clientY - canvasRect.top; | |
| const length = Math.sqrt(Math.pow(toX - fromX, 2) + Math.pow(toY - fromY, 2)); | |
| const angle = Math.atan2(toY - fromY, toX - fromX) * 180 / Math.PI; | |
| tempLine.style.width = `${length}px`; | |
| tempLine.style.left = `${fromX}px`; | |
| tempLine.style.top = `${fromY}px`; | |
| tempLine.style.transform = `rotate(${angle}deg)`; | |
| }); | |
| document.addEventListener('mouseup', (e) => { | |
| if (!isDragging || !startConnector) { | |
| isDragging = false; | |
| startConnector = null; | |
| return; | |
| } | |
| const connector = e.target.closest('.node-connector'); | |
| if (connector) { | |
| const endConnector = { | |
| node: connector.dataset.node, | |
| type: connector.dataset.type | |
| }; | |
| // Only connect output to input | |
| if (startConnector.type === 'output' && endConnector.type === 'input') { | |
| // Don't allow self-connections | |
| if (startConnector.node !== endConnector.node) { | |
| createConnection(startConnector.node, endConnector.node); | |
| } | |
| } | |
| } | |
| // Clean up | |
| if (tempLine) { | |
| tempLine.remove(); | |
| tempLine = null; | |
| } | |
| isDragging = false; | |
| startConnector = null; | |
| }); | |
| // Clear canvas | |
| clearCanvasBtn.addEventListener('click', () => { | |
| if (confirm('Are you sure you want to clear the canvas?')) { | |
| document.querySelectorAll('.canvas-node').forEach(node => node.remove()); | |
| document.querySelectorAll('.connection-line').forEach(line => line.remove()); | |
| nodes = []; | |
| connections = []; | |
| updateNodeCount(); | |
| } | |
| }); | |
| // Save workflow | |
| saveWorkflowBtn.addEventListener('click', () => { | |
| const workflow = { | |
| nodes: nodes.map(node => ({ | |
| id: node.id, | |
| type: node.type, | |
| subtype: node.subtype, | |
| position: { | |
| x: parseInt(node.element.style.left), | |
| y: parseInt(node.element.style.top) | |
| } | |
| })), | |
| connections: connections | |
| }; | |
| // In a real app, you would send this to a server | |
| console.log('Workflow saved:', workflow); | |
| alert('Workflow saved successfully!'); | |
| }); | |
| // Zoom functionality | |
| document.getElementById('zoom-in').addEventListener('click', () => { | |
| canvasScale = Math.min(canvasScale + 0.1, 2); | |
| canvas.style.transform = `scale(${canvasScale})`; | |
| }); | |
| document.getElementById('zoom-out').addEventListener('click', () => { | |
| canvasScale = Math.max(canvasScale - 0.1, 0.5); | |
| canvas.style.transform = `scale(${canvasScale})`; | |
| }); | |
| document.getElementById('center-canvas').addEventListener('click', () => { | |
| canvasScale = 1; | |
| canvas.style.transform = `scale(${canvasScale})`; | |
| canvas.scrollIntoView({ behavior: 'smooth', block: 'center' }); | |
| }); | |
| // Initialize | |
| updateNodeCount(); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=designfailure/insur-mcp" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |