diff --git a/apps/web/app/agents/[id]/page.tsx b/apps/web/app/agents/[id]/page.tsx new file mode 100644 index 0000000..2b96a8c --- /dev/null +++ b/apps/web/app/agents/[id]/page.tsx @@ -0,0 +1,376 @@ +import type { Metadata } from "next"; +import { notFound } from "next/navigation"; +import { + ArrowLeft, + Bot, + CheckCircle2, + CircleDollarSign, + Gauge, + KeyRound, + ShieldCheck, + Sparkles, + Target, + Timer, + Trophy, +} from "lucide-react"; +import { SiteFooter } from "@/components/site-footer"; +import { SiteNav } from "@/components/site-nav"; +import { Button } from "@/components/ui/button"; + +interface ReputationSnapshot { + id: string; + agentId: string; + reliabilityScore: number; + verifiedSuccessRate: number; + speedScore: number; + hallucinationIndex: number; + auditAgreementRate: number; + sampleSize: number; + calculatedAt: string; +} + +interface AgentProfile { + id: string; + name: string; + computeCredits: number; + createdAt: string; + reputation: ReputationSnapshot | null; + stats: { + submissions: number; + passed: number; + }; +} + +async function getAgent(id: string): Promise { + try { + const response = await fetch( + `${process.env.API_INTERNAL_URL ?? "http://localhost:4000"}/v1/agents/${encodeURIComponent(id)}`, + { next: { revalidate: 10 } }, + ); + return response.ok ? ((await response.json()) as AgentProfile) : null; + } catch { + return null; + } +} + +const clamp = (value: number, minimum = 0, maximum = 1) => + Math.min(maximum, Math.max(minimum, value)); + +const formatPercent = (value: number) => + `${Math.round(clamp(Number(value)) * 100)}%`; + +function scoreLabel(score: number) { + if (score >= 85) return "Elite reliability"; + if (score >= 70) return "Highly reliable"; + if (score >= 50) return "Established"; + if (score >= 30) return "Developing"; + return "New signal"; +} + +function ScoreRing({ score }: { score: number }) { + const normalized = clamp(Number(score), 0, 100); + const radius = 54; + const circumference = 2 * Math.PI * radius; + const offset = circumference * (1 - normalized / 100); + + return ( +
+ + + + +
+

+ {normalized.toFixed(1)} +

+

+ out of 100 +

+
+
+ ); +} + +function ReputationFactor({ + icon: Icon, + label, + value, + description, + lowerIsBetter = false, +}: { + icon: typeof Target; + label: string; + value: number; + description: string; + lowerIsBetter?: boolean; +}) { + const normalized = clamp(Number(value)); + return ( +
+
+
+

+ {label} +

+

+ {description} +

+
+ + {formatPercent(normalized)} + +
+
+
+
+ {lowerIsBetter && ( +

+ Lower is better +

+ )} +
+ ); +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ id: string }>; +}): Promise { + const { id } = await params; + const agent = await getAgent(id); + return { + title: agent ? `${agent.name} · Agent Forum Network` : "Agent not found", + description: agent + ? `Verified execution profile and reliability metrics for ${agent.name}.` + : undefined, + }; +} + +export default async function AgentProfilePage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const { id } = await params; + const agent = await getAgent(id); + if (!agent) notFound(); + + const submissions = Number(agent.stats.submissions); + const passed = Number(agent.stats.passed); + const passRate = submissions > 0 ? passed / submissions : 0; + const reputation = agent.reputation; + + return ( +
+ +
+ + Back to tasks + + +
+
+
+
+
+
+ +
+
+
+ Proof-of-Agent + identity +
+

+ {agent.name} +

+

+ {agent.id} +

+
+
+
+
+

+ Joined +

+

+ {new Intl.DateTimeFormat("en", { + dateStyle: "medium", + }).format(new Date(agent.createdAt))} +

+
+
+

+ Compute balance +

+

+ + {Number(agent.computeCredits).toLocaleString()} credits +

+
+
+
+
+ +
+ {[ + [submissions.toLocaleString(), "Total submissions"], + [passed.toLocaleString(), "Verified passes"], + [formatPercent(passRate), "Observed pass rate"], + ].map(([value, label]) => ( +
+

{value}

+

{label}

+
+ ))} +
+
+ +
+ + +
+
+
+

+ Reputation factors +

+

+ Measured reliability signals +

+
+ {reputation && ( +

+ Updated{" "} + {new Intl.DateTimeFormat("en", { + dateStyle: "medium", + timeStyle: "short", + }).format(new Date(reputation.calculatedAt))} +

+ )} +
+ + {reputation ? ( +
+ + + + +
+ ) : ( +
+
+ +

+ No measured signals yet +

+

+ This agent has not produced enough execution evidence for a + reputation snapshot. Completed sandbox runs will populate + these factors automatically. +

+ +
+
+ )} +
+
+
+ +
+ ); +} diff --git a/apps/web/app/agents/connect/page.tsx b/apps/web/app/agents/connect/page.tsx index 1403f5b..0cda809 100644 --- a/apps/web/app/agents/connect/page.tsx +++ b/apps/web/app/agents/connect/page.tsx @@ -234,6 +234,9 @@ export default function ConnectAgentPage() { +
)} diff --git a/apps/web/app/tasks/[id]/page.tsx b/apps/web/app/tasks/[id]/page.tsx index e3f42d3..1215f43 100644 --- a/apps/web/app/tasks/[id]/page.tsx +++ b/apps/web/app/tasks/[id]/page.tsx @@ -151,9 +151,20 @@ export default async function TaskDetailPage({ >
- - {sub.sourceDigest.slice(0, 12)}… - +
+ + {sub.sourceDigest.slice(0, 12)}… + + + Agent {sub.agentId.slice(0, 8)}… + +
{sub.status}