Spaces:
Sleeping
Sleeping
| """ | |
| FastAPI Web Application | |
| ======================== | |
| Developer console and API endpoints for the multi-agent system. | |
| """ | |
| import sys | |
| import os | |
| from pathlib import Path | |
| # Add src to path | |
| sys.path.insert(0, str(Path(__file__).parent.parent)) | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.responses import HTMLResponse, JSONResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from pydantic import BaseModel | |
| from typing import Any, Optional | |
| from orchestrator import Orchestrator | |
| from ledger.merkle import compute_merkle_root | |
| app = FastAPI( | |
| title="AgentMask Developer Console", | |
| description="Multi-agent system with audit trail", | |
| version="0.1.0" | |
| ) | |
| class TaskRequest(BaseModel): | |
| """Request model for running a task.""" | |
| query: str | |
| options: Optional[dict[str, Any]] = None | |
| class TaskResponse(BaseModel): | |
| """Response model for task execution.""" | |
| success: bool | |
| task: dict[str, Any] | |
| steps: list[dict[str, Any]] | |
| final_output: dict[str, Any] | |
| merkle_root: str | |
| total_steps: int | |
| # Global orchestrator instance | |
| orchestrator = Orchestrator() | |
| async def get_console(): | |
| """ | |
| Serve the developer console HTML page. | |
| """ | |
| html_content = """ | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AgentMask Developer Console</title> | |
| <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Share+Tech+Mono&display=swap'); | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'JetBrains Mono', 'Consolas', monospace; | |
| background: #0a0a0a; | |
| background-image: | |
| radial-gradient(ellipse at top, #0d1a0d 0%, transparent 50%), | |
| radial-gradient(ellipse at bottom, #1a0d0d 0%, transparent 50%), | |
| repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0, 255, 65, 0.03) 2px, rgba(0, 255, 65, 0.03) 4px); | |
| min-height: 100vh; | |
| color: #00ff41; | |
| padding: 20px; | |
| } | |
| body::before { | |
| content: ''; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: repeating-linear-gradient( | |
| 0deg, | |
| rgba(0, 0, 0, 0.15), | |
| rgba(0, 0, 0, 0.15) 1px, | |
| transparent 1px, | |
| transparent 2px | |
| ); | |
| pointer-events: none; | |
| z-index: 1000; | |
| } | |
| .container { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| position: relative; | |
| } | |
| h1 { | |
| text-align: center; | |
| margin-bottom: 30px; | |
| color: #00ff41; | |
| text-shadow: | |
| 0 0 5px #00ff41, | |
| 0 0 10px #00ff41, | |
| 0 0 20px #00ff41, | |
| 0 0 40px #00cc33; | |
| font-family: 'Share Tech Mono', monospace; | |
| letter-spacing: 3px; | |
| animation: flicker 3s infinite; | |
| } | |
| @keyframes flicker { | |
| 0%, 100% { opacity: 1; } | |
| 92% { opacity: 1; } | |
| 93% { opacity: 0.8; } | |
| 94% { opacity: 1; } | |
| 95% { opacity: 0.9; } | |
| 96% { opacity: 1; } | |
| } | |
| .input-section { | |
| background: rgba(0, 20, 0, 0.8); | |
| border: 1px solid #00ff41; | |
| border-radius: 0; | |
| padding: 20px; | |
| margin-bottom: 20px; | |
| box-shadow: | |
| 0 0 10px rgba(0, 255, 65, 0.3), | |
| inset 0 0 30px rgba(0, 255, 65, 0.05); | |
| } | |
| .input-group { | |
| display: flex; | |
| gap: 10px; | |
| } | |
| input[type="text"] { | |
| flex: 1; | |
| padding: 12px 20px; | |
| border: 1px solid #00ff41; | |
| border-radius: 0; | |
| background: rgba(0, 0, 0, 0.9); | |
| color: #00ff41; | |
| font-size: 16px; | |
| font-family: 'JetBrains Mono', monospace; | |
| text-shadow: 0 0 5px #00ff41; | |
| } | |
| input[type="text"]::placeholder { | |
| color: #006622; | |
| } | |
| input[type="text"]:focus { | |
| outline: none; | |
| box-shadow: | |
| 0 0 10px rgba(0, 255, 65, 0.5), | |
| 0 0 20px rgba(0, 255, 65, 0.3); | |
| border-color: #39ff14; | |
| } | |
| button { | |
| padding: 12px 30px; | |
| background: transparent; | |
| border: 2px solid #ff0040; | |
| border-radius: 0; | |
| color: #ff0040; | |
| font-size: 16px; | |
| font-weight: bold; | |
| font-family: 'Share Tech Mono', monospace; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| text-transform: uppercase; | |
| letter-spacing: 2px; | |
| text-shadow: 0 0 5px #ff0040; | |
| } | |
| button:hover { | |
| background: #ff0040; | |
| color: #000; | |
| box-shadow: | |
| 0 0 20px rgba(255, 0, 64, 0.6), | |
| 0 0 40px rgba(255, 0, 64, 0.4); | |
| text-shadow: none; | |
| } | |
| button:disabled { | |
| border-color: #333; | |
| color: #333; | |
| cursor: not-allowed; | |
| text-shadow: none; | |
| box-shadow: none; | |
| } | |
| .main-content { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 20px; | |
| } | |
| @media (max-width: 1000px) { | |
| .main-content { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| .results-section, .graph-section { | |
| background: rgba(0, 20, 0, 0.8); | |
| border: 1px solid #00ff41; | |
| border-radius: 0; | |
| padding: 20px; | |
| box-shadow: | |
| 0 0 10px rgba(0, 255, 65, 0.2), | |
| inset 0 0 50px rgba(0, 255, 65, 0.03); | |
| } | |
| .graph-section h2 { | |
| color: #00ff41; | |
| margin-bottom: 15px; | |
| text-shadow: 0 0 10px #00ff41; | |
| font-family: 'Share Tech Mono', monospace; | |
| } | |
| .mermaid { | |
| background: #0d0d0d; | |
| border: 1px solid #00ff41; | |
| border-radius: 0; | |
| padding: 20px; | |
| min-height: 200px; | |
| } | |
| .merkle-root { | |
| background: rgba(255, 0, 64, 0.1); | |
| border: 1px solid #ff0040; | |
| border-radius: 0; | |
| padding: 15px; | |
| margin-bottom: 20px; | |
| font-family: monospace; | |
| word-break: break-all; | |
| box-shadow: 0 0 10px rgba(255, 0, 64, 0.2); | |
| } | |
| .merkle-root label { | |
| color: #ff0040; | |
| font-weight: bold; | |
| text-shadow: 0 0 5px #ff0040; | |
| } | |
| .step { | |
| background: rgba(0, 0, 0, 0.5); | |
| border-radius: 0; | |
| padding: 15px; | |
| margin-bottom: 15px; | |
| border-left: 3px solid #00ff41; | |
| border-right: 1px solid #003311; | |
| border-top: 1px solid #003311; | |
| border-bottom: 1px solid #003311; | |
| } | |
| .step-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 10px; | |
| } | |
| .step-number { | |
| background: #ff0040; | |
| color: #000; | |
| padding: 5px 15px; | |
| border-radius: 0; | |
| font-weight: bold; | |
| font-family: 'Share Tech Mono', monospace; | |
| box-shadow: 0 0 10px rgba(255, 0, 64, 0.5); | |
| } | |
| .agent-name { | |
| color: #39ff14; | |
| font-weight: bold; | |
| font-size: 18px; | |
| text-shadow: 0 0 10px #39ff14; | |
| text-transform: uppercase; | |
| letter-spacing: 2px; | |
| } | |
| .step-hash { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 11px; | |
| color: #006622; | |
| word-break: break-all; | |
| } | |
| .step-content { | |
| margin-top: 10px; | |
| } | |
| .step-content pre { | |
| background: rgba(0, 0, 0, 0.8); | |
| padding: 10px; | |
| border-radius: 0; | |
| border: 1px solid #003311; | |
| overflow-x: auto; | |
| font-size: 12px; | |
| color: #00cc33; | |
| } | |
| .loading { | |
| text-align: center; | |
| padding: 40px; | |
| color: #00ff41; | |
| text-shadow: 0 0 10px #00ff41; | |
| } | |
| .loading::after { | |
| content: ''; | |
| animation: dots 1.5s infinite; | |
| } | |
| @keyframes dots { | |
| 0%, 20% { content: '_'; } | |
| 40% { content: '__'; } | |
| 60%, 100% { content: '___'; } | |
| } | |
| .error { | |
| background: rgba(255, 0, 0, 0.15); | |
| border: 1px solid #ff0040; | |
| border-radius: 0; | |
| padding: 15px; | |
| color: #ff3366; | |
| text-shadow: 0 0 5px #ff0040; | |
| } | |
| .summary { | |
| background: rgba(0, 255, 65, 0.1); | |
| border: 1px solid #00ff41; | |
| border-radius: 0; | |
| padding: 15px; | |
| margin-top: 20px; | |
| box-shadow: 0 0 15px rgba(0, 255, 65, 0.2); | |
| } | |
| .summary h3 { | |
| color: #39ff14; | |
| margin-bottom: 10px; | |
| text-shadow: 0 0 10px #39ff14; | |
| } | |
| .tab-container { | |
| margin-bottom: 15px; | |
| } | |
| .tab-buttons { | |
| display: flex; | |
| gap: 5px; | |
| margin-bottom: 10px; | |
| } | |
| .tab-btn { | |
| padding: 8px 16px; | |
| background: transparent; | |
| border: 1px solid #00ff41; | |
| border-radius: 0; | |
| color: #00ff41; | |
| cursor: pointer; | |
| font-size: 14px; | |
| font-family: 'Share Tech Mono', monospace; | |
| transition: all 0.2s; | |
| } | |
| .tab-btn:hover { | |
| background: rgba(0, 255, 65, 0.1); | |
| box-shadow: 0 0 10px rgba(0, 255, 65, 0.3); | |
| } | |
| .tab-btn.active { | |
| background: #00ff41; | |
| color: #000; | |
| box-shadow: 0 0 15px rgba(0, 255, 65, 0.5); | |
| } | |
| .tab-content { | |
| display: none; | |
| } | |
| .tab-content.active { | |
| display: block; | |
| } | |
| /* Terminal cursor blink */ | |
| h1::after { | |
| content: 'β'; | |
| animation: blink 1s infinite; | |
| } | |
| @keyframes blink { | |
| 0%, 50% { opacity: 1; } | |
| 51%, 100% { opacity: 0; } | |
| } | |
| /* Scrollbar styling */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: #0a0a0a; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: #00ff41; | |
| border-radius: 0; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: #39ff14; | |
| } | |
| /* Selection color */ | |
| ::selection { | |
| background: #00ff41; | |
| color: #000; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>β AgentMask Terminal </h1> | |
| <div class="input-section"> | |
| <div class="input-group"> | |
| <input type="text" id="queryInput" placeholder="[root@agentmask]$ Enter query..." value="AI in healthcare diagnosis"> | |
| <button id="runBtn" onclick="runTask()">βΊ EXECUTE</button> | |
| </div> | |
| </div> | |
| <div class="main-content"> | |
| <div class="results-section" id="results"> | |
| <p style="text-align: center; color: #006622;">> Awaiting command input...<br>> Type query and execute to initialize agent pipeline_</p> | |
| </div> | |
| <div class="graph-section"> | |
| <h2>> AGENT PIPELINE GRAPH_</h2> | |
| <div class="tab-container"> | |
| <div class="tab-buttons"> | |
| <button class="tab-btn active" onclick="showTab('flow')">Flow</button> | |
| <button class="tab-btn" onclick="showTab('sequence')">Sequence</button> | |
| <button class="tab-btn" onclick="showTab('merkle')">Merkle Tree</button> | |
| </div> | |
| <div id="flow-tab" class="tab-content active"> | |
| <div id="flowGraph" class="mermaid"> | |
| graph LR | |
| A[π Input Query] --> B[π Research Agent] | |
| B --> C[π Summarizer Agent] | |
| C --> D[β Output] | |
| </div> | |
| </div> | |
| <div id="sequence-tab" class="tab-content"> | |
| <div id="sequenceGraph" class="mermaid"> | |
| sequenceDiagram | |
| participant U as User | |
| participant O as Orchestrator | |
| participant R as Research | |
| participant S as Summarizer | |
| U->>O: Submit Query | |
| O->>R: Execute Search | |
| R-->>O: Search Results | |
| O->>S: Summarize Results | |
| S-->>O: Summary | |
| O-->>U: Final Output | |
| </div> | |
| </div> | |
| <div id="merkle-tab" class="tab-content"> | |
| <div id="merkleGraph" class="mermaid"> | |
| graph TB | |
| Root[π Merkle Root] | |
| Root --> H1[Hash 1-2] | |
| Root --> H2[Hash 3-4] | |
| H1 --> L1[Step 1 Hash] | |
| H1 --> L2[Step 2 Hash] | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| mermaid.initialize({ | |
| startOnLoad: true, | |
| theme: 'dark', | |
| themeVariables: { | |
| primaryColor: '#00ff41', | |
| primaryTextColor: '#000', | |
| primaryBorderColor: '#00cc33', | |
| lineColor: '#00ff41', | |
| secondaryColor: '#ff0040', | |
| tertiaryColor: '#0a0a0a', | |
| background: '#0a0a0a', | |
| mainBkg: '#0d1a0d', | |
| textColor: '#00ff41', | |
| nodeTextColor: '#000', | |
| nodeBorder: '#00ff41', | |
| clusterBkg: '#0d1a0d', | |
| clusterBorder: '#00ff41', | |
| edgeLabelBackground: '#0a0a0a' | |
| } | |
| }); | |
| function showTab(tabName) { | |
| // Hide all tabs | |
| document.querySelectorAll('.tab-content').forEach(tab => { | |
| tab.classList.remove('active'); | |
| }); | |
| document.querySelectorAll('.tab-btn').forEach(btn => { | |
| btn.classList.remove('active'); | |
| }); | |
| // Show selected tab | |
| document.getElementById(tabName + '-tab').classList.add('active'); | |
| event.target.classList.add('active'); | |
| } | |
| async function runTask() { | |
| const query = document.getElementById('queryInput').value.trim(); | |
| const resultsDiv = document.getElementById('results'); | |
| const runBtn = document.getElementById('runBtn'); | |
| if (!query) { | |
| resultsDiv.innerHTML = `<div class="error">> ERROR: Please enter a query.</div>`; | |
| return; | |
| } | |
| runBtn.disabled = true; | |
| resultsDiv.innerHTML = '<div class="loading">> Initializing agent pipeline</div>'; | |
| try { | |
| const response = await fetch('/run', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ query: query }) | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| displayResults(data); | |
| updateGraphs(data); | |
| } catch (error) { | |
| resultsDiv.innerHTML = `<div class="error">> FATAL: ${error.message}</div>`; | |
| } finally { | |
| runBtn.disabled = false; | |
| } | |
| } | |
| function displayResults(data) { | |
| const resultsDiv = document.getElementById('results'); | |
| let html = ''; | |
| // Merkle Root | |
| html += ` | |
| <div class="merkle-root"> | |
| <label>π MERKLE ROOT HASH:</label> | |
| <div style="margin-top: 5px; color: #ff0040;">${data.merkle_root}</div> | |
| </div> | |
| `; | |
| // Steps | |
| html += '<h2 style="margin-bottom: 15px; color: #00ff41; text-shadow: 0 0 10px #00ff41;">> EXECUTION LOG_</h2>'; | |
| for (const step of data.steps) { | |
| html += ` | |
| <div class="step"> | |
| <div class="step-header"> | |
| <span class="step-number">STEP ${step.step}</span> | |
| <span class="agent-name">[${step.agent.toUpperCase()}]</span> | |
| </div> | |
| <div class="step-hash">SHA256: ${step.hash}</div> | |
| <div class="step-content"> | |
| <details> | |
| <summary style="cursor: pointer; color: #39ff14;">> View I/O Data_</summary> | |
| <h4 style="margin: 10px 0 5px; color: #ff0040;">INPUT:</h4> | |
| <pre>${JSON.stringify(step.input, null, 2)}</pre> | |
| <h4 style="margin: 10px 0 5px; color: #ff0040;">OUTPUT:</h4> | |
| <pre>${JSON.stringify(step.output, null, 2)}</pre> | |
| </details> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| // Final Summary | |
| if (data.final_output && data.final_output.summary) { | |
| html += ` | |
| <div class="summary"> | |
| <h3>> FINAL OUTPUT_</h3> | |
| <p style="color: #00ff41;">${data.final_output.summary}</p> | |
| </div> | |
| `; | |
| } | |
| resultsDiv.innerHTML = html; | |
| } | |
| function updateGraphs(data) { | |
| // Update flow graph with actual steps | |
| const steps = data.steps; | |
| let flowDef = 'graph LR\\n'; | |
| flowDef += ' A[π Input Query]'; | |
| steps.forEach((step, i) => { | |
| const prev = i === 0 ? 'A' : `S${i}`; | |
| const curr = `S${i + 1}`; | |
| const icon = step.agent === 'research' ? 'π' : 'π'; | |
| flowDef += ` --> ${curr}[${icon} ${step.agent.charAt(0).toUpperCase() + step.agent.slice(1)}]`; | |
| }); | |
| flowDef += ' --> Z[β Output]'; | |
| // Update merkle tree visualization | |
| let merkleDef = 'graph TB\\n'; | |
| merkleDef += ` Root[π ${data.merkle_root.substring(0, 12)}...]\\n`; | |
| steps.forEach((step, i) => { | |
| merkleDef += ` Root --> H${i + 1}[Step ${i + 1}: ${step.hash.substring(0, 8)}...]\\n`; | |
| }); | |
| // Re-render mermaid graphs | |
| const flowEl = document.getElementById('flowGraph'); | |
| const merkleEl = document.getElementById('merkleGraph'); | |
| flowEl.innerHTML = flowDef; | |
| merkleEl.innerHTML = merkleDef; | |
| mermaid.init(undefined, flowEl); | |
| mermaid.init(undefined, merkleEl); | |
| } | |
| // Allow Enter key to submit | |
| document.getElementById('queryInput').addEventListener('keypress', function(e) { | |
| if (e.key === 'Enter') { | |
| runTask(); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| return HTMLResponse(content=html_content) | |
| async def run_task(request: TaskRequest): | |
| """ | |
| Execute a task through the multi-agent pipeline. | |
| Args: | |
| request: TaskRequest with query and optional parameters | |
| Returns: | |
| TaskResponse with execution results and merkle root | |
| """ | |
| try: | |
| # Run the orchestrator | |
| result = await orchestrator.run_task({"query": request.query}) | |
| # Compute merkle root from all step hashes | |
| step_hashes = [step["hash"] for step in result["steps"]] | |
| merkle_root = compute_merkle_root(step_hashes) if step_hashes else "" | |
| return TaskResponse( | |
| success=True, | |
| task=result["task"], | |
| steps=result["steps"], | |
| final_output=result["final_output"], | |
| merkle_root=merkle_root, | |
| total_steps=result["total_steps"] | |
| ) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def health_check(): | |
| """Health check endpoint.""" | |
| return {"status": "healthy", "version": "0.1.0"} | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=8000) | |