Spaces:
Runtime error
Runtime error
| import { useState, useRef, useCallback } from "react"; | |
| interface Finding { | |
| title: string; | |
| description: string; | |
| recommendation: string; | |
| severity: "high" | "medium" | "low"; | |
| codeSnippet: string; | |
| location: string; | |
| path: string; | |
| judgeReview: { | |
| review: string; | |
| confidence: number; | |
| exploitablePaths: string[]; | |
| }; | |
| } | |
| interface AgentResult { | |
| contract?: string; | |
| compilationErrors?: string[]; | |
| reviewSummary?: string; | |
| findings?: Finding[]; | |
| // Tester fields | |
| status?: string; | |
| pocCode?: string; | |
| executionLogs?: string[]; | |
| iterations?: number; | |
| } | |
| export function App() { | |
| const [requirements, setRequirements] = useState(""); | |
| const [logs, setLogs] = useState<string[]>([]); | |
| const [coderResult, setCoderResult] = useState<AgentResult | null>(null); | |
| const [auditorResult, setAuditorResult] = useState<AgentResult | null>(null); | |
| const [testerResult, setTesterResult] = useState<AgentResult | null>(null); | |
| const [running, setRunning] = useState(false); | |
| const logsEndRef = useRef<HTMLDivElement>(null); | |
| const appendLog = useCallback((msg: string) => { | |
| setLogs((prev) => [...prev, msg]); | |
| setTimeout(() => logsEndRef.current?.scrollIntoView({ behavior: "smooth" }), 50); | |
| }, []); | |
| const handleSubmit = async (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| if (!requirements.trim() || running) return; | |
| setRunning(true); | |
| setLogs([]); | |
| setCoderResult(null); | |
| setAuditorResult(null); | |
| setTesterResult(null); | |
| appendLog("Iniciando pipeline..."); | |
| try { | |
| const res = await fetch("/api/run", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ requirements }), | |
| }); | |
| const reader = res.body?.getReader(); | |
| if (!reader) throw new Error("Stream não disponível"); | |
| const decoder = new TextDecoder(); | |
| let buffer = ""; | |
| let currentEvent = ""; | |
| while (true) { | |
| const { done, value } = await reader.read(); | |
| if (done) break; | |
| buffer += decoder.decode(value, { stream: true }); | |
| const lines = buffer.split("\n"); | |
| buffer = lines.pop() || ""; | |
| for (const line of lines) { | |
| if (line.startsWith("event:")) { | |
| currentEvent = line.slice(6).trim(); | |
| console.log("event", currentEvent); | |
| } else if (line.startsWith("data:")) { | |
| const data = line.slice(5).trim(); | |
| console.log("data", data); | |
| switch (currentEvent) { | |
| case "log": | |
| appendLog(data); | |
| break; | |
| case "coder": | |
| setCoderResult(JSON.parse(data)); | |
| break; | |
| case "auditor": | |
| setAuditorResult(JSON.parse(data)); | |
| break; | |
| case "tester": | |
| setTesterResult(JSON.parse(data)); | |
| break; | |
| case "error": | |
| appendLog(`❌ ERRO: ${data}`); | |
| break; | |
| } | |
| currentEvent = ""; | |
| } | |
| } | |
| } | |
| } catch (err) { | |
| appendLog(`❌ Erro de conexão: ${err instanceof Error ? err.message : String(err)}`); | |
| } finally { | |
| setRunning(false); | |
| } | |
| }; | |
| return ( | |
| <div style={styles.container}> | |
| <header style={styles.header}> | |
| <h1 style={styles.title}>Multi-Agent: Geração, Auditoria e Teste de Smart Contracts</h1> | |
| <p style={styles.subtitle}> | |
| Descreva um cenário ou requisito cuja solução seja um smart contract em Solidity. O sistema irá gerar, | |
| compilar, auditar e testar o contrato automaticamente. | |
| </p> | |
| </header> | |
| <form onSubmit={handleSubmit} style={styles.form}> | |
| <textarea | |
| value={requirements} | |
| onChange={(e) => setRequirements(e.target.value)} | |
| placeholder={ | |
| "Ex: Crie um token ERC20 com as seguintes características:\n- Nome: MeuToken, Símbolo: MTK\n- Supply inicial de 1.000.000 tokens\n- Funções de mint (apenas owner) e burn\n- Pausável pelo owner" | |
| } | |
| style={styles.textarea} | |
| rows={6} | |
| disabled={running} | |
| /> | |
| <button | |
| type="submit" | |
| disabled={running || !requirements.trim()} | |
| style={{ | |
| ...styles.button, | |
| opacity: running || !requirements.trim() ? 0.5 : 1, | |
| }} | |
| > | |
| {running ? "Executando pipeline..." : "Executar Pipeline"} | |
| </button> | |
| </form> | |
| {/* Logs */} | |
| {logs.length > 0 && ( | |
| <section style={styles.section}> | |
| <h2 style={styles.sectionTitle}>📋 Log de Execução</h2> | |
| <div style={styles.logBox}> | |
| {logs.map((log, i) => ( | |
| <div key={i} style={styles.logLine}> | |
| {log} | |
| </div> | |
| ))} | |
| <div ref={logsEndRef} /> | |
| </div> | |
| </section> | |
| )} | |
| {/* Coder */} | |
| {coderResult && ( | |
| <section style={styles.section}> | |
| <h2 style={styles.sectionTitle}>🔨 Agente Coder</h2> | |
| <h3 style={styles.subTitle}>Contrato Gerado</h3> | |
| <div style={styles.codeBox}> | |
| <pre style={styles.code}>{coderResult.contract}</pre> | |
| </div> | |
| {coderResult.compilationErrors && coderResult.compilationErrors.length > 0 && ( | |
| <> | |
| <h3 style={{ ...styles.subTitle, color: "#ef4444" }}>Erros de Compilação</h3> | |
| <div style={{ ...styles.codeBox, borderColor: "#ef4444" }}> | |
| <pre style={styles.code}>{coderResult.compilationErrors.join("\n")}</pre> | |
| </div> | |
| </> | |
| )} | |
| {coderResult.reviewSummary && ( | |
| <> | |
| <h3 style={styles.subTitle}>Revisão de Segurança</h3> | |
| <div style={styles.resultBox}> | |
| <p style={styles.resultText}>{coderResult.reviewSummary}</p> | |
| </div> | |
| </> | |
| )} | |
| </section> | |
| )} | |
| {/* Auditor */} | |
| {auditorResult && ( | |
| <section style={styles.section}> | |
| <h2 style={styles.sectionTitle}>🔍 Agente Auditor</h2> | |
| {auditorResult.findings && auditorResult.findings.length > 0 ? ( | |
| auditorResult.findings.map((f, i) => ( | |
| <div key={i} style={{ ...styles.findingCard, borderColor: severityColor(f.severity) }}> | |
| <div style={styles.findingHeader}> | |
| <span style={{ ...styles.severityBadge, background: severityColor(f.severity) }}> | |
| {f.severity.toUpperCase()} | |
| </span> | |
| <span style={styles.findingTitle}>{f.title}</span> | |
| </div> | |
| <p style={styles.findingText}>{f.description}</p> | |
| <p style={{ ...styles.findingText, color: "#94a3b8" }}> | |
| <strong>Localização:</strong> {f.location ?? "-"} | |
| </p> | |
| {f.codeSnippet && <pre style={styles.code}>{f.codeSnippet}</pre>} | |
| <p style={{ ...styles.findingText, color: "#94a3b8" }}> | |
| <strong>Recomendação:</strong> {f.recommendation} | |
| </p> | |
| <p style={{ ...styles.findingText, color: "#64748b", fontSize: 12 }}> | |
| Confiança: {Math.round(f.judgeReview.confidence)}% — {f.judgeReview.review} | |
| </p> | |
| </div> | |
| )) | |
| ) : ( | |
| <div style={styles.resultBox}> | |
| <p style={styles.resultText}>Nenhuma vulnerabilidade encontrada.</p> | |
| </div> | |
| )} | |
| </section> | |
| )} | |
| {/* Tester */} | |
| {testerResult && ( | |
| <section style={styles.section}> | |
| <h2 style={styles.sectionTitle}>🧪 Agente Tester</h2> | |
| <div style={styles.resultBox}> | |
| <p style={styles.resultText}> | |
| <strong>Status:</strong>{" "} | |
| <span style={{ color: testerResult.status === "success" ? "#22c55e" : "#ef4444" }}> | |
| {testerResult.status?.toUpperCase()} | |
| </span> | |
| <br /> | |
| <strong>Iterações:</strong> {testerResult.iterations} | |
| </p> | |
| </div> | |
| {testerResult.pocCode && ( | |
| <> | |
| <h3 style={styles.subTitle}>Proof of Concept (Exploit)</h3> | |
| <div style={styles.codeBox}> | |
| <pre style={styles.code}>{testerResult.pocCode}</pre> | |
| </div> | |
| </> | |
| )} | |
| {testerResult.executionLogs && testerResult.executionLogs.length > 0 && ( | |
| <> | |
| <h3 style={styles.subTitle}>Logs de Execução (Foundry)</h3> | |
| <div style={styles.logBox}> | |
| {testerResult.executionLogs.map((log, i) => ( | |
| <div key={i} style={styles.logLine}> | |
| {log} | |
| </div> | |
| ))} | |
| </div> | |
| </> | |
| )} | |
| </section> | |
| )} | |
| </div> | |
| ); | |
| } | |
| const severityColor = (severity: string) => { | |
| switch (severity) { | |
| case "high": | |
| return "#ef4444"; | |
| case "medium": | |
| return "#f97316"; | |
| case "low": | |
| return "#eab308"; | |
| default: | |
| return "#64748b"; | |
| } | |
| }; | |
| const styles: Record<string, React.CSSProperties> = { | |
| container: { | |
| maxWidth: 900, | |
| margin: "0 auto", | |
| padding: "32px 20px", | |
| fontFamily: "'Segoe UI', system-ui, -apple-system, sans-serif", | |
| color: "#e2e8f0", | |
| background: "#0f172a", | |
| minHeight: "100vh", | |
| }, | |
| header: { | |
| textAlign: "center", | |
| marginBottom: 32, | |
| }, | |
| title: { | |
| fontSize: 28, | |
| fontWeight: 700, | |
| color: "#f8fafc", | |
| margin: "0 0 12px", | |
| lineHeight: 1.3, | |
| }, | |
| subtitle: { | |
| fontSize: 15, | |
| color: "#94a3b8", | |
| margin: 0, | |
| lineHeight: 1.6, | |
| }, | |
| form: { | |
| display: "flex", | |
| flexDirection: "column", | |
| gap: 12, | |
| marginBottom: 32, | |
| }, | |
| textarea: { | |
| width: "100%", | |
| padding: 16, | |
| fontSize: 14, | |
| fontFamily: "inherit", | |
| borderRadius: 8, | |
| border: "1px solid #334155", | |
| background: "#1e293b", | |
| color: "#e2e8f0", | |
| resize: "vertical", | |
| outline: "none", | |
| boxSizing: "border-box", | |
| lineHeight: 1.6, | |
| }, | |
| button: { | |
| padding: "12px 24px", | |
| fontSize: 15, | |
| fontWeight: 600, | |
| borderRadius: 8, | |
| border: "none", | |
| background: "#3b82f6", | |
| color: "#fff", | |
| cursor: "pointer", | |
| transition: "background 0.2s", | |
| }, | |
| section: { | |
| marginBottom: 28, | |
| }, | |
| sectionTitle: { | |
| fontSize: 18, | |
| fontWeight: 600, | |
| color: "#f8fafc", | |
| marginBottom: 10, | |
| }, | |
| subTitle: { | |
| fontSize: 14, | |
| fontWeight: 600, | |
| color: "#94a3b8", | |
| marginTop: 14, | |
| marginBottom: 6, | |
| }, | |
| logBox: { | |
| background: "#1e293b", | |
| border: "1px solid #334155", | |
| borderRadius: 8, | |
| padding: 16, | |
| maxHeight: 220, | |
| overflowY: "auto", | |
| fontSize: 13, | |
| fontFamily: "'JetBrains Mono', 'Fira Code', monospace", | |
| }, | |
| logLine: { | |
| padding: "2px 0", | |
| color: "#a5f3fc", | |
| whiteSpace: "pre-wrap", | |
| wordBreak: "break-word", | |
| }, | |
| codeBox: { | |
| background: "#1e293b", | |
| border: "1px solid #334155", | |
| borderRadius: 8, | |
| padding: 16, | |
| maxHeight: 400, | |
| overflowY: "auto", | |
| }, | |
| code: { | |
| margin: 0, | |
| fontSize: 13, | |
| fontFamily: "'JetBrains Mono', 'Fira Code', monospace", | |
| color: "#a5f3fc", | |
| whiteSpace: "pre-wrap", | |
| wordBreak: "break-word", | |
| }, | |
| resultBox: { | |
| background: "#1e293b", | |
| border: "1px solid #334155", | |
| borderRadius: 8, | |
| padding: 16, | |
| }, | |
| resultText: { | |
| margin: 0, | |
| fontSize: 14, | |
| lineHeight: 1.6, | |
| color: "#cbd5e1", | |
| whiteSpace: "pre-wrap", | |
| }, | |
| findingCard: { | |
| background: "#1e293b", | |
| border: "1px solid", | |
| borderRadius: 8, | |
| padding: 16, | |
| marginBottom: 12, | |
| }, | |
| findingHeader: { | |
| display: "flex", | |
| alignItems: "center", | |
| gap: 10, | |
| marginBottom: 8, | |
| }, | |
| severityBadge: { | |
| fontSize: 11, | |
| fontWeight: 700, | |
| color: "#fff", | |
| padding: "2px 8px", | |
| borderRadius: 4, | |
| letterSpacing: "0.05em", | |
| }, | |
| findingTitle: { | |
| fontSize: 15, | |
| fontWeight: 600, | |
| color: "#f8fafc", | |
| }, | |
| findingText: { | |
| margin: "4px 0", | |
| fontSize: 13, | |
| lineHeight: 1.6, | |
| color: "#cbd5e1", | |
| }, | |
| }; | |