| | <!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>NeuroArch Studio | AI Architecture & Prompt Engineering Suite</title> |
| | |
| | <link rel="preconnect" href="https://fonts.googleapis.com"> |
| | <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| | <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet"> |
| | |
| | <style> |
| | :root { |
| | |
| | --bg-deep: #050507; |
| | --bg-panel: #0f1115; |
| | --bg-surface: #181b21; |
| | --primary: #3b82f6; |
| | --primary-glow: rgba(59, 130, 246, 0.5); |
| | --accent: #8b5cf6; |
| | --accent-glow: rgba(139, 92, 246, 0.5); |
| | --success: #10b981; |
| | --warning: #f59e0b; |
| | --text-main: #e2e8f0; |
| | --text-muted: #94a3b8; |
| | --border: #2d3748; |
| | --grid-line: rgba(255, 255, 255, 0.03); |
| | |
| | |
| | --radius-md: 8px; |
| | --radius-lg: 16px; |
| | --header-height: 64px; |
| | } |
| | |
| | * { |
| | box-sizing: border-box; |
| | margin: 0; |
| | padding: 0; |
| | } |
| | |
| | body { |
| | font-family: 'Inter', sans-serif; |
| | background-color: var(--bg-deep); |
| | color: var(--text-main); |
| | height: 100vh; |
| | overflow: hidden; |
| | display: flex; |
| | flex-direction: column; |
| | } |
| | |
| | |
| | header { |
| | height: var(--header-height); |
| | background: rgba(15, 17, 21, 0.8); |
| | backdrop-filter: blur(12px); |
| | border-bottom: 1px solid var(--border); |
| | display: flex; |
| | align-items: center; |
| | justify-content: space-between; |
| | padding: 0 24px; |
| | z-index: 100; |
| | } |
| | |
| | .brand { |
| | display: flex; |
| | align-items: center; |
| | gap: 12px; |
| | font-weight: 700; |
| | font-size: 1.25rem; |
| | letter-spacing: -0.025em; |
| | background: linear-gradient(to right, var(--primary), var(--accent)); |
| | -webkit-background-clip: text; |
| | -webkit-text-fill-color: transparent; |
| | } |
| | |
| | .brand-icon { |
| | width: 32px; |
| | height: 32px; |
| | background: linear-gradient(135deg, var(--primary), var(--accent)); |
| | border-radius: var(--radius-md); |
| | display: grid; |
| | place-items: center; |
| | color: white; |
| | font-size: 1.2rem; |
| | } |
| | |
| | .header-actions { |
| | display: flex; |
| | align-items: center; |
| | gap: 20px; |
| | } |
| | |
| | a.built-with { |
| | color: var(--text-muted); |
| | text-decoration: none; |
| | font-size: 0.85rem; |
| | transition: color 0.2s; |
| | display: flex; |
| | align-items: center; |
| | gap: 6px; |
| | } |
| | |
| | a.built-with:hover { |
| | color: var(--primary); |
| | } |
| | |
| | |
| | main { |
| | flex: 1; |
| | display: grid; |
| | grid-template-columns: 280px 1fr 320px; |
| | height: calc(100vh - var(--header-height)); |
| | position: relative; |
| | } |
| | |
| | |
| | .sidebar { |
| | background: var(--bg-panel); |
| | border-right: 1px solid var(--border); |
| | padding: 20px; |
| | display: flex; |
| | flex-direction: column; |
| | gap: 24px; |
| | overflow-y: auto; |
| | } |
| | |
| | .tool-group h3 { |
| | font-size: 0.75rem; |
| | text-transform: uppercase; |
| | letter-spacing: 0.05em; |
| | color: var(--text-muted); |
| | margin-bottom: 12px; |
| | } |
| | |
| | .node-btn { |
| | width: 100%; |
| | background: var(--bg-surface); |
| | border: 1px solid var(--border); |
| | color: var(--text-main); |
| | padding: 12px; |
| | border-radius: var(--radius-md); |
| | cursor: pointer; |
| | text-align: left; |
| | transition: all 0.2s ease; |
| | display: flex; |
| | align-items: center; |
| | gap: 10px; |
| | margin-bottom: 8px; |
| | font-size: 0.9rem; |
| | } |
| | |
| | .node-btn:hover { |
| | border-color: var(--primary); |
| | background: rgba(59, 130, 246, 0.05); |
| | transform: translateX(4px); |
| | } |
| | |
| | .node-btn .icon { |
| | width: 24px; |
| | height: 24px; |
| | background: rgba(255,255,255,0.1); |
| | border-radius: 4px; |
| | display: flex; |
| | align-items: center; |
| | justify-content: center; |
| | font-size: 0.8rem; |
| | } |
| | |
| | |
| | .canvas-container { |
| | background-color: var(--bg-deep); |
| | background-image: |
| | linear-gradient(var(--grid-line) 1px, transparent 1px), |
| | linear-gradient(90deg, var(--grid-line) 1px, transparent 1px); |
| | background-size: 40px 40px; |
| | position: relative; |
| | overflow: hidden; |
| | user-select: none; |
| | cursor: grab; |
| | } |
| | |
| | .canvas-container:active { |
| | cursor: grabbing; |
| | } |
| | |
| | |
| | #connections-layer { |
| | position: absolute; |
| | top: 0; |
| | left: 0; |
| | width: 100%; |
| | height: 100%; |
| | pointer-events: none; |
| | z-index: 1; |
| | } |
| | |
| | .connection-path { |
| | fill: none; |
| | stroke: var(--border); |
| | stroke-width: 2px; |
| | transition: stroke 0.3s; |
| | } |
| | |
| | .connection-path.active { |
| | stroke: var(--primary); |
| | stroke-dasharray: 10; |
| | animation: flowAnimation 1s linear infinite; |
| | } |
| | |
| | @keyframes flowAnimation { |
| | from { stroke-dashoffset: 20; } |
| | to { stroke-dashoffset: 0; } |
| | } |
| | |
| | |
| | .node { |
| | position: absolute; |
| | width: 180px; |
| | background: rgba(24, 27, 33, 0.9); |
| | backdrop-filter: blur(8px); |
| | border: 1px solid var(--border); |
| | border-radius: var(--radius-md); |
| | padding: 16px; |
| | z-index: 2; |
| | cursor: pointer; |
| | box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); |
| | transition: box-shadow 0.2s, border-color 0.2s; |
| | } |
| | |
| | .node:hover { |
| | border-color: var(--text-muted); |
| | } |
| | |
| | .node.selected { |
| | border-color: var(--primary); |
| | box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2), 0 10px 15px -3px rgba(0, 0, 0, 0.5); |
| | } |
| | |
| | .node-header { |
| | display: flex; |
| | justify-content: space-between; |
| | align-items: center; |
| | margin-bottom: 8px; |
| | font-weight: 600; |
| | font-size: 0.9rem; |
| | } |
| | |
| | .node-status { |
| | width: 8px; |
| | height: 8px; |
| | border-radius: 50%; |
| | background-color: var(--text-muted); |
| | } |
| | |
| | .node-status.active { background-color: var(--success); box-shadow: 0 0 8px var(--success); } |
| | .node-status.processing { background-color: var(--warning); box-shadow: 0 0 8px var(--warning); } |
| | |
| | .node-body { |
| | font-size: 0.8rem; |
| | color: var(--text-muted); |
| | font-family: 'JetBrains Mono', monospace; |
| | } |
| | |
| | .node-ports { |
| | position: absolute; |
| | width: 12px; |
| | height: 12px; |
| | background: var(--text-main); |
| | border-radius: 50%; |
| | border: 2px solid var(--bg-surface); |
| | cursor: crosshair; |
| | } |
| | .port-in { top: -6px; left: 50%; transform: translateX(-50%); } |
| | .port-out { bottom: -6px; left: 50%; transform: translateX(-50%); } |
| | |
| | |
| | .properties-panel { |
| | background: var(--bg-panel); |
| | border-left: 1px solid var(--border); |
| | padding: 20px; |
| | overflow-y: auto; |
| | display: flex; |
| | flex-direction: column; |
| | gap: 20px; |
| | } |
| | |
| | .panel-header { |
| | font-size: 1rem; |
| | font-weight: 600; |
| | padding-bottom: 12px; |
| | border-bottom: 1px solid var(--border); |
| | } |
| | |
| | .prop-group { |
| | display: flex; |
| | flex-direction: column; |
| | gap: 8px; |
| | } |
| | |
| | .prop-group label { |
| | font-size: 0.8rem; |
| | color: var(--text-muted); |
| | } |
| | |
| | .prop-input, .prop-select, .prop-textarea { |
| | background: var(--bg-surface); |
| | border: 1px solid var(--border); |
| | color: var(--text-main); |
| | padding: 10px; |
| | border-radius: var(--radius-md); |
| | font-family: inherit; |
| | font-size: 0.9rem; |
| | outline: none; |
| | transition: border-color 0.2s; |
| | } |
| | |
| | .prop-input:focus, .prop-select:focus, .prop-textarea:focus { |
| | border-color: var(--primary); |
| | } |
| | |
| | .prop-textarea { |
| | resize: vertical; |
| | min-height: 80px; |
| | font-family: 'JetBrains Mono', monospace; |
| | } |
| | |
| | .action-btn { |
| | background: var(--primary); |
| | color: white; |
| | border: none; |
| | padding: 12px; |
| | border-radius: var(--radius-md); |
| | font-weight: 600; |
| | cursor: pointer; |
| | transition: background 0.2s; |
| | margin-top: auto; |
| | } |
| | |
| | .action-btn:hover { |
| | background: #2563eb; |
| | } |
| | |
| | .action-btn.secondary { |
| | background: transparent; |
| | border: 1px solid var(--border); |
| | color: var(--text-main); |
| | } |
| | |
| | .action-btn.secondary:hover { |
| | border-color: var(--text-muted); |
| | } |
| | |
| | |
| | .console-output { |
| | background: #000; |
| | border-radius: var(--radius-md); |
| | padding: 12px; |
| | font-family: 'JetBrains Mono', monospace; |
| | font-size: 0.75rem; |
| | height: 150px; |
| | overflow-y: auto; |
| | color: #33ff00; |
| | border: 1px solid #333; |
| | margin-top: 20px; |
| | } |
| | |
| | .log-entry { margin-bottom: 4px; } |
| | .log-time { color: #666; margin-right: 8px; } |
| | .log-info { color: #3b82f6; } |
| | .log-warn { color: #f59e0b; } |
| | .log-success { color: #10b981; } |
| | |
| | |
| | .toast-container { |
| | position: fixed; |
| | bottom: 24px; |
| | right: 24px; |
| | z-index: 1000; |
| | display: flex; |
| | flex-direction: column; |
| | gap: 10px; |
| | } |
| | |
| | .toast { |
| | background: var(--bg-surface); |
| | border: 1px solid var(--border); |
| | border-left: 4px solid var(--primary); |
| | padding: 16px; |
| | border-radius: var(--radius-md); |
| | min-width: 300px; |
| | box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5); |
| | animation: slideIn 0.3s ease-out; |
| | display: flex; |
| | align-items: center; |
| | justify-content: space-between; |
| | } |
| | |
| | @keyframes slideIn { |
| | from { transform: translateX(100%); opacity: 0; } |
| | to { transform: translateX(0); opacity: 1; } |
| | } |
| | |
| | |
| | @media (max-width: 1024px) { |
| | main { |
| | grid-template-columns: 240px 1fr; |
| | } |
| | .properties-panel { |
| | position: absolute; |
| | right: 0; |
| | top: 0; |
| | bottom: 0; |
| | width: 300px; |
| | transform: translateX(100%); |
| | transition: transform 0.3s; |
| | z-index: 50; |
| | background: var(--bg-panel); |
| | } |
| | .properties-panel.open { |
| | transform: translateX(0); |
| | } |
| | } |
| | |
| | @media (max-width: 768px) { |
| | main { |
| | grid-template-columns: 1fr; |
| | } |
| | .sidebar { |
| | display: none; |
| | } |
| | |
| | } |
| | </style> |
| | </head> |
| | <body> |
| |
|
| | <header> |
| | <div class="brand"> |
| | <div class="brand-icon">N</div> |
| | <span>NeuroArch Studio</span> |
| | </div> |
| | <div class="header-actions"> |
| | <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with"> |
| | <span>Built with anycoder</span> |
| | <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| | <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path> |
| | <polyline points="15 3 21 3 21 9"></polyline> |
| | <line x1="10" y1="14" x2="21" y2="3"></line> |
| | </svg> |
| | </a> |
| | </div> |
| | </header> |
| |
|
| | <main> |
| | |
| | <aside class="sidebar"> |
| | <div class="tool-group"> |
| | <h3>Data Ingestion</h3> |
| | <button class="node-btn" onclick="app.addNode('input')"> |
| | <div class="icon">📥</div> Data Source |
| | </button> |
| | <button class="node-btn" onclick="app.addNode('vector-db')"> |
| | <div class="icon">🗃️</div> Vector Store |
| | </button> |
| | </div> |
| |
|
| | <div class="tool-group"> |
| | <h3>AI Processing</h3> |
| | <button class="node-btn" onclick="app.addNode('llm')"> |
| | <div class="icon">🧠</div> LLM Core |
| | </button> |
| | <button class="node-btn" onclick="app.addNode('embedder')"> |
| | <div class="icon">🔢</div> Embeddings |
| | </button> |
| | </div> |
| |
|
| | <div class="tool-group"> |
| | <h3>Output & Logic</h3> |
| | <button class="node-btn" onclick="app.addNode('router')"> |
| | <div class="icon">🔀</div> Semantic Router |
| | </button> |
| | <button class="node-btn" onclick="app.addNode('output')"> |
| | <div class="icon">📤</div> Response |
| | </button> |
| | </div> |
| | |
| | <div style="margin-top: auto;"> |
| | <button class="action-btn secondary" onclick="app.clearCanvas()" style="width: 100%; font-size: 0.8rem; padding: 8px;">Clear Canvas</button> |
| | </div> |
| | </aside> |
| |
|
| | |
| | <section class="canvas-container" id="canvas"> |
| | <svg id="connections-layer"></svg> |
| | |
| | </section> |
| |
|
| | |
| | <aside class="properties-panel" id="properties-panel"> |
| | <div class="panel-header">Properties</div> |
| | |
| | <div id="no-selection-msg" style="color: var(--text-muted); font-size: 0.9rem; text-align: center; margin-top: 40px;"> |
| | Select a node to configure its parameters. |
| | </div> |
| |
|
| | <div id="prop-form" style="display: none; flex-direction: column; height: 100%;"> |
| | <div class="prop-group"> |
| | <label>Node ID</label> |
| | <input type="text" id="prop-id" class="prop-input" disabled> |
| | </div> |
| |
|
| | <div class="prop-group"> |
| | <label>Label</label> |
| | <input type="text" id="prop-label" class="prop-input"> |
| | </div> |
| |
|
| | <div class="prop-group" id="group-model"> |
| | <label>Model</label> |
| | <select id="prop-model" class="prop-select"> |
| | <option value="gpt-4-turbo">GPT-4 Turbo</option> |
| | <option value="gpt-3.5-turbo">GPT-3.5 Turbo</option> |
| | <option value="claude-3-opus">Claude 3 Opus</option> |
| | <option value="mistral-large">Mistral Large</option> |
| | <option value="llama-3-70b">Llama 3 70B</option> |
| | </select> |
| | </div> |
| |
|
| | <div class="prop-group" id="group-temp"> |
| | <label>Temperature (0.0 - 1.0)</label> |
| | <input type="range" id="prop-temp" min="0" max="1" step="0.1" value="0.7" style="width: 100%"> |
| | <div style="display: flex; justify-content: space-between; font-size: 0.75rem; color: var(--text-muted);"> |
| | <span>Precise</span> |
| | <span id="temp-val">0.7</span> |
| | <span>Creative</span> |
| | </div> |
| | </div> |
| |
|
| | <div class="prop-group"> |
| | <label>System Prompt / Context</label> |
| | <textarea id="prop-prompt" class="prop-textarea" placeholder="Enter system instructions..."></textarea> |
| | </div> |
| |
|
| | <button class="action-btn" onclick="app.runSimulation()">Run Simulation</button> |
| | <button class="action-btn secondary" onclick="app.deleteSelectedNode()">Delete Node</button> |
| | </div> |
| |
|
| | <div class="console-output" id="console-logs"> |
| | <div class="log-entry"><span class="log-time">[System]</span> Ready. Awaiting architecture input.</div> |
| | </div> |
| | </aside> |
| | </main> |
| |
|
| | <div class="toast-container" id="toast-container"></div> |
| |
|
| | <script> |
| | |
| | |
| | |
| | |
| | |
| | |
| | class NeuroArchApp { |
| | constructor() { |
| | this.nodes = []; |
| | this.connections = []; |
| | this.selectedNodeId = null; |
| | this.canvas = document.getElementById('canvas'); |
| | this.svgLayer = document.getElementById('connections-layer'); |
| | this.isDragging = false; |
| | this.dragNodeId = null; |
| | this.dragOffset = { x: 0, y: 0 }; |
| | |
| | |
| | this.propPanel = document.getElementById('prop-form'); |
| | this.noSelectionMsg = document.getElementById('no-selection-msg'); |
| | this.consoleLogs = document.getElementById('console-logs'); |
| | |
| | |
| | this.canvas.addEventListener('mousedown', (e) => this.handleCanvasMouseDown(e)); |
| | document.addEventListener('mousemove', (e) => this.handleMouseMove(e)); |
| | document.addEventListener('mouseup', (e) => this.handleMouseUp(e)); |
| | |
| | |
| | this.addNode('input', 100, 150); |
| | } |
| | |
| | |
| | |
| | |
| | generateId() { |
| | return 'node_' + Math.random().toString(36).substr(2, 9); |
| | } |
| | |
| | |
| | |
| | |
| | log(message, type = 'info') { |
| | const time = new Date().toLocaleTimeString('en-US', { hour12: false }); |
| | const entry = document.createElement('div'); |
| | entry.className = 'log-entry'; |
| | entry.innerHTML = `<span class="log-time">[${time}]</span> <span class="log-${type}">${message}</span>`; |
| | this.consoleLogs.appendChild(entry); |
| | this.consoleLogs.scrollTop = this.consoleLogs.scrollHeight; |
| | } |
| | |
| | |
| | |
| | |
| | addNode(type, x = 50, y = 50) { |
| | const id = this.generateId(); |
| | const nodeData = { |
| | id, |
| | type, |
| | x, |
| | y, |
| | label: this.getDefaultLabel(type), |
| | model: type === 'llm' ? 'gpt-4-turbo' : 'N/A', |
| | temp: 0.7, |
| | prompt: '' |
| | }; |
| | |
| | this.nodes.push(nodeData); |
| | this.renderNode(nodeData); |
| | this.log(`Added node: ${nodeData.label} (${type})`, 'info'); |
| | return id; |
| | } |
| | |
| | getDefaultLabel(type) { |
| | const labels = { |
| | 'input': 'Data Source', |
| | 'vector-db': 'Vector DB', |
| | 'llm': 'LLM Processor', |
| | 'embedder': 'Text Embedder', |
| | 'router': 'Semantic Router', |
| | 'output': 'Final Output' |
| | }; |
| | return labels[type] || 'Unknown Node'; |
| | } |
| | |
| | |
| | |
| | |
| | renderNode(node) { |
| | const el = document.createElement('div'); |
| | el.className = 'node'; |
| | el.id = node.id; |
| | el.style.left = `${node.x}px`; |
| | el.style.top = `${node.y}px`; |
| | |
| | |
| | el.innerHTML = ` |
| | <div class="node-ports port-in" data-port="in" title="Input"></div> |
| | <div class="node-header"> |
| | <span>${node.label}</span> |
| | <div class="node-status" id="status-${node.id}"></div> |
| | </div> |
| | <div class="node-body"> |
| | ${node.type.toUpperCase()}<br> |
| | Latency: <span id="latency-${node.id}">--</span>ms |
| | </div> |
| | <div class="node-ports port-out" data-port="out" title="Output"></div> |
| | `; |
| | |
| | |
| | el.addEventListener('mousedown', (e) => { |
| | if (e.target.classList.contains('node-ports')) return; |
| | this.selectNode(node.id); |
| | this.isDragging = true; |
| | this.dragNodeId = node.id; |
| | const rect = el.getBoundingClientRect(); |
| | this.dragOffset.x = e.clientX - rect.left; |
| | this.dragOffset.y = e.clientY - rect.top; |
| | }); |
| | |
| | this.canvas.appendChild(el); |
| | |
| | |
| | el.animate([ |
| | { transform: 'scale(0.8)', opacity: 0 }, |
| | { transform: 'scale(1)', opacity: 1 } |
| | ], { duration: 200, easing: 'ease-out' }); |
| | } |
| | |
| | |
| | |
| | |
| | handleCanvasMouseDown(e) { |
| | if (e.target === this.canvas || e.target === this.svgLayer) { |
| | this.deselectAll(); |
| | } |
| | } |
| | |
| | handleMouseMove(e) { |
| | if (this.isDragging && this.dragNodeId) { |
| | const node = this.nodes.find(n => n.id === this.dragNodeId); |
| | if (node) { |
| | |
| | const canvasRect = this.canvas.getBoundingClientRect(); |
| | let newX = e.clientX - canvasRect.left - this.dragOffset.x; |
| | let newY = e.clientY - canvasRect.top - this.dragOffset.y; |
| | |
| | |
| | newX = Math.max(0, newX); |
| | newY = Math.max(0, newY); |
| | |
| | node.x = newX; |
| | node.y = newY; |
| | |
| | |
| | const el = document.getElementById(node.id); |
| | el.style.left = `${newX}px`; |
| | el.style.top = `${newY}px`; |
| | |
| | this.updateConnections(); |
| | } |
| | } |
| | } |
| | |
| | handleMouseUp(e) { |
| | this.isDragging = false; |
| | this.dragNodeId = null; |
| | } |
| | |
| | |
| | |
| | |
| | selectNode(id) { |
| | this.selectedNodeId = id; |
| | |
| | |
| | document.querySelectorAll('.node').forEach(n => n.classList.remove('selected')); |
| | const el = document.getElementById(id); |
| | if(el) el.classList.add('selected'); |
| | |
| | |
| | const node = this.nodes.find(n => n.id === id); |
| | if (node) { |
| | this.noSelectionMsg.style.display = 'none'; |
| | this.propPanel.style.display = 'flex'; |
| | |
| | |
| | document.getElementById('prop-id').value = node.id; |
| | document.getElementById('prop-label').value = node.label; |
| | document.getElementById('prop-temp').value = node.temp; |
| | document.getElementById('temp-val').innerText = node.temp; |
| | document.getElementById('prop-prompt').value = node.prompt; |
| | |
| | const modelSelect = document.getElementById('prop-model'); |
| | if(node.type === 'llm') { |
| | document.getElementById('group-model').style.display = 'flex'; |
| | document.getElementById('group-temp').style.display = 'flex'; |
| | modelSelect.value = node.model; |
| | } else { |
| | document.getElementById('group-model').style.display = 'none'; |
| | document.getElementById('group-temp').style.display = 'none'; |
| | } |
| | |
| | |
| | this.setupPropertyListeners(node); |
| | } |
| | } |
| | |
| | setupPropertyListeners(node) { |
| | const labelInput = document.getElementById('prop-label'); |
| | const tempInput = document.getElementById('prop-temp'); |
| | const promptInput = document.getElementById('prop-prompt'); |
| | const modelSelect = document.getElementById('prop-model'); |
| | |
| | |
| | const newLabelInput = labelInput.cloneNode(true); |
| | labelInput.parentNode.replaceChild(newLabelInput, labelInput); |
| | newLabelInput.addEventListener('input', (e) => { |
| | node.label = e.target.value; |
| | document.querySelector(`#${node.id} .node-header span`).innerText = node.label; |
| | }); |
| | |
| | const newTempInput = tempInput.cloneNode(true); |
| | tempInput.parentNode.replaceChild(newTempInput, tempInput); |
| | newTempInput.addEventListener('input', (e) => { |
| | node.temp = e.target.value; |
| | document.getElementById('temp-val').innerText = node.temp; |
| | }); |
| | |
| | const newPromptInput = promptInput.cloneNode(true); |
| | promptInput.parentNode.replaceChild(newPromptInput, promptInput); |
| | newPromptInput.addEventListener('input', (e) => { |
| | node.prompt = e.target.value; |
| | }); |
| | |
| | const newModelSelect = modelSelect.cloneNode(true); |
| | modelSelect.parentNode.replaceChild(newModelSelect, modelSelect); |
| | newModelSelect.addEventListener('change', (e) => { |
| | node.model = e.target.value; |
| | this.log(`Updated model for ${node.id} to ${node.model}`, 'info'); |
| | }); |
| | } |
| | |
| | deselectAll() { |
| | this.selectedNodeId = null; |
| | document.querySelectorAll('.node').forEach(n => n.classList.remove('selected')); |
| | this.noSelectionMsg.style.display = 'block'; |
| | this.propPanel.style.display = 'none'; |
| | } |
| | |
| | |
| | |
| | |
| | connectNodes(sourceId, targetId) { |
| | |
| | const exists = this.connections.find(c => c.from === sourceId && c.to === targetId); |
| | if (!exists) { |
| | this.connections.push({ from: sourceId, to: targetId }); |
| | this.updateConnections(); |
| | } |
| | } |
| | |
| | |
| | |
| | |
| | updateConnections() { |
| | this.svgLayer.innerHTML = ''; |
| | |
| | this.connections.forEach(conn => { |
| | const fromNode = document.getElementById(conn.from); |
| | const toNode = document.getElementById(conn.to); |
| | |
| | if (fromNode && toNode) { |
| | const fromRect = fromNode.getBoundingClientRect(); |
| | const toRect = toNode.getBoundingClientRect(); |
| | const canvasRect = this.canvas.getBoundingClientRect(); |
| | |
| | |
| | const x1 = fromRect.left - canvasRect.left + fromRect.width / 2; |
| | const y1 = fromRect.top - canvasRect.top + fromRect.height; |
| | const x2 = toRect.left - canvasRect.left + toRect.width / 2; |
| | const y2 = toRect.top - canvasRect.top; |
| | |
| | |
| | const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); |
| | const controlY1 = y1 + 50; |
| | const controlY2 = y2 - 50; |
| | |
| | const d = `M ${x1} ${y1} C ${x1} ${controlY1}, ${x2} ${controlY2}, ${x2} ${y2}`; |
| | |
| | path.setAttribute('d', d); |
| | path.setAttribute('class', 'connection-path'); |
| | path.setAttribute('id', `conn-${conn.from}-${conn.to}`); |
| | |
| | this.svgLayer.appendChild(path); |
| | } |
| | }); |
| | } |
| | |
| | deleteSelectedNode() { |
| | if(!this.selectedNodeId) return; |
| | |
| | |
| | const el = document.getElementById(this.selectedNodeId); |
| | if(el) el.remove(); |
| | |
| | |
| | this.nodes = this.nodes.filter(n => n.id !== this.selectedNodeId); |
| | this.connections = this.connections.filter(c => c.from !== this.selectedNodeId && c.to !== this.selectedNodeId); |
| | |
| | this.log(`Deleted node ${this.selectedNodeId}`, 'warn'); |
| | this.deselectAll(); |
| | this.updateConnections(); |
| | } |
| | |
| | clearCanvas() { |
| | this.nodes.forEach(n => { |
| | const el = document.getElementById(n.id); |
| | if(el) el.remove(); |
| | }); |
| | this.nodes = []; |
| | this.connections = []; |
| | this.svgLayer.innerHTML = ''; |
| | this.log('Canvas cleared.', 'info'); |
| | this.deselectAll(); |
| | } |
| | |
| | |
| | |
| | |
| | async runSimulation() { |
| | if (this.connections.length === 0) { |
| | this.showToast('No connections detected. Connect nodes to run pipeline.', 'warning'); |
| | return; |
| | } |
| | |
| | this.log('Initializing pipeline simulation...', 'info'); |
| | |
| | |
| | document.querySelectorAll('.node-status').forEach(el => { |
| | el.className = 'node-status'; |
| | }); |
| | document.querySelectorAll('.connection-path').forEach(el => el.classList.remove('active')); |
| | |
| | |
| | |
| | |
| | const visited = new Set(); |
| | const queue = this.nodes.filter(n => this.connections.some(c => c.from === n.id)); |
| | |
| | |
| | |
| | |
| | for (const node of this.nodes) { |
| | const statusEl = document.getElementById(`status-${node.id}`); |
| | statusEl.classList.add('processing'); |
| | |
| | |
| | const latency = Math.floor(Math.random() * 200) + 50; |
| | document.getElementById(`latency-${node.id}`).innerText = latency; |
| | |
| | await new Promise(r => setTimeout(r, latency)); |
| | |
| | statusEl.classList.remove('processing'); |
| | statusEl.classList.add('active'); |
| | |
| | |
| | this.connections |
| | .filter(c => c.from === node.id) |
| | .forEach(c => { |
| | const path = document.getElementById(`conn-${c.from}-${c.to}`); |
| | if(path) path.classList.add('active'); |
| | }); |
| | |
| | this.log(`Executed ${node.label}: ${latency}ms`, 'success'); |
| | } |
| | |
| | this.showToast('Pipeline execution completed successfully.', 'success'); |
| | } |
| | |
| | showToast(message, type = 'info') { |
| | const container = document.getElementById('toast-container'); |
| | const toast = document.createElement('div'); |
| | toast.className = 'toast'; |
| | toast.style.borderLeftColor = type === 'success' ? 'var(--success)' : (type === 'warning' ? 'var(--warning)' : 'var(--primary)'); |
| | |
| | toast.innerHTML = ` |
| | <span>${message}</span> |
| | <button onclick="this.parentElement.remove()" style="background:none;border:none;color:white;cursor:pointer;">✕</button> |
| | `; |
| | |
| | container.appendChild(toast); |
| | |
| | |
| | setTimeout(() => { |
| | toast.style.opacity = '0'; |
| | setTimeout(() => toast.remove(), 300); |
| | }, 3000); |
| | } |
| | } |
| | |
| | |
| | const app = new NeuroArchApp(); |
| | |
| | </script> |
| | </body> |
| | </html> |