Tales-Cunha
fix: revert the changes from the auditor agent and update the poc agent
d26fc11
Raw
History Blame
12.2 kB
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",
},
};