This commit is contained in:
2026-04-05 00:43:23 +05:30
commit 8be37d3e92
425 changed files with 101853 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
"use client";
import { ConcessionEntry } from "@/lib/types";
interface Props {
concessions: ConcessionEntry[];
}
export default function ConcessionTimeline({ concessions }: Props) {
if (!concessions || concessions.length === 0) {
return (
<div className="text-xs text-slate-600 py-4 text-center font-mono">
No concessions recorded yet
</div>
);
}
return (
<div className="relative flex flex-col gap-3">
{/* Data stream line */}
<div className="absolute left-[14px] top-4 bottom-4 data-stream-line" />
{concessions.map((c, i) => {
const isA = c.by === "A";
return (
<div key={i} className="flex items-center gap-3">
{/* Node */}
<div
className={`relative z-10 size-7 rounded-full shrink-0 flex items-center justify-center text-[9px] font-mono font-bold border ${
isA
? "bg-[#B7A6FB]/10 border-[#B7A6FB]/30 text-[#B7A6FB]"
: "bg-cyan-900/20 border-cyan-500/30 text-cyan-400"
}`}
>
{c.by}
</div>
{/* Card */}
<div
className={`flex-1 flex items-center justify-between gap-3 px-3 py-2 rounded-lg border backdrop-blur-sm text-xs ${
isA
? "bg-[#B7A6FB]/5 border-[#B7A6FB]/15"
: "bg-cyan-900/5 border-cyan-500/15"
}`}
>
<div className="flex items-center gap-2 min-w-0">
<span
className={`text-[9px] font-mono px-1.5 py-0.5 rounded border shrink-0 ${
isA
? "text-[#B7A6FB] bg-[#B7A6FB]/10 border-[#B7A6FB]/20"
: "text-cyan-400 bg-cyan-500/10 border-cyan-500/20"
}`}
>
Agent {c.by}
</span>
<span className="material-symbols-outlined text-slate-600 text-sm">arrow_forward</span>
<span className="text-slate-300 truncate">{c.gave_up}</span>
</div>
<span className="text-[9px] text-slate-600 font-mono shrink-0">Rd {c.round}</span>
</div>
</div>
);
})}
</div>
);
}
interface Props {
concessions: ConcessionEntry[];
}

View File

@@ -0,0 +1,65 @@
"use client";
interface Props {
score: number;
satA?: number;
satB?: number;
}
export default function FairnessScore({ score, satA, satB }: Props) {
const pct = Math.min(100, Math.max(0, score));
const color =
pct >= 80 ? "text-[#B7A6FB]" : pct >= 60 ? "text-amber-400" : "text-red-400";
const barColor =
pct >= 80
? "bg-[#B7A6FB] shadow-[0_0_8px_#B7A6FB]"
: pct >= 60
? "bg-amber-400"
: "bg-red-400";
return (
<div className="space-y-4">
{/* Big score */}
<div className="flex items-end justify-between">
<div>
<p className="text-[10px] text-slate-500 font-mono uppercase tracking-wider mb-1">Fairness Score</p>
<span className={`text-4xl font-light tabular-nums ${color} text-glow`}>
{pct.toFixed(0)}
</span>
<span className="text-base font-light text-slate-600 ml-1">/100</span>
</div>
<div className={`text-[10px] font-bold font-mono px-2 py-1 rounded border ${
pct >= 80
? "text-[#B7A6FB] bg-[#B7A6FB]/10 border-[#B7A6FB]/20"
: pct >= 60
? "text-amber-400 bg-amber-500/10 border-amber-500/20"
: "text-red-400 bg-red-500/10 border-red-500/20"
}`}>
{pct >= 80 ? "FAIR" : pct >= 60 ? "MODERATE" : "SKEWED"}
</div>
</div>
{/* Progress bar */}
<div className="w-full h-1.5 bg-white/5 rounded-full overflow-hidden">
<div
className={`h-full rounded-full transition-all duration-700 ${barColor}`}
style={{ width: `${pct}%` }}
/>
</div>
{/* Per-agent */}
{satA !== undefined && satB !== undefined && (
<div className="grid grid-cols-2 gap-3">
<div className="bg-[#B7A6FB]/5 border border-[#B7A6FB]/20 rounded-lg p-3 text-center hover:border-[#B7A6FB]/40 transition-colors">
<div className="text-[10px] text-slate-500 font-mono mb-1">Agent A</div>
<div className="text-xl font-light text-[#B7A6FB] tabular-nums">{satA.toFixed(0)}%</div>
</div>
<div className="bg-cyan-900/10 border border-cyan-500/20 rounded-lg p-3 text-center hover:border-cyan-500/40 transition-colors">
<div className="text-[10px] text-slate-500 font-mono mb-1">Agent B</div>
<div className="text-xl font-light text-cyan-400 tabular-nums">{satB.toFixed(0)}%</div>
</div>
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,161 @@
"use client";
import { Round, Participant, Personality } from "@/lib/types";
import { PERSONALITY_LABELS } from "@/lib/utils";
interface Props {
rounds: Round[];
participants: Participant[];
}
function Icon({ name, className = "" }: { name: string; className?: string }) {
return <span className={`material-symbols-outlined ${className}`}>{name}</span>;
}
const ACTION_ICON: Record<string, string> = {
propose: "send",
counter: "swap_horiz",
accept: "check_circle",
escalate: "warning",
};
const ACTION_LABEL: Record<string, string> = {
propose: "Proposed",
counter: "Counter",
accept: "Accepted",
escalate: "Escalated",
};
const ACTION_BADGE: Record<string, string> = {
propose: "text-[#B7A6FB] bg-[#B7A6FB]/10 border-[#B7A6FB]/20",
counter: "text-slate-300 bg-white/5 border-white/10",
accept: "text-emerald-400 bg-emerald-500/10 border-emerald-500/20",
escalate: "text-amber-400 bg-amber-500/10 border-amber-500/20",
};
export default function NegotiationTimeline({ rounds, participants }: Props) {
if (!rounds || rounds.length === 0) {
return (
<div className="text-xs text-slate-600 py-6 text-center font-mono">
Negotiation hasn&apos;t started yet
</div>
);
}
const userA = participants?.[0];
const userB = participants?.[1];
function agentLabel(proposerId: number) {
if (userA && proposerId === userA.user_id) return "A";
if (userB && proposerId === userB.user_id) return "B";
return "?";
}
function agentPersonality(proposerId: number): Personality {
const p = participants?.find((p) => p.user_id === proposerId);
return (p?.personality_used ?? "balanced") as Personality;
}
return (
<div className="relative flex flex-col gap-6">
{/* Vertical data-stream line */}
<div className="absolute left-[18px] top-6 bottom-6 data-stream-line" />
{rounds.map((round, idx) => {
const label = agentLabel(round.proposer_id);
const personality = agentPersonality(round.proposer_id);
const action = round.response_type ?? "propose";
const isA = label === "A";
const isLast = idx === rounds.length - 1;
return (
<div key={round.id} className="flex gap-4 group">
{/* Node */}
<div className="relative z-10 flex flex-col items-center shrink-0">
<div
className={`size-9 rounded-full flex items-center justify-center text-[10px] font-mono font-bold transition-all ${
isLast
? isA
? "bg-[#B7A6FB] text-black shadow-[0_0_12px_#B7A6FB]"
: "bg-cyan-400 text-black shadow-[0_0_12px_#22d3ee]"
: "bg-[#070312] border border-white/10 text-slate-500"
}`}
>
{String(round.round_number).padStart(2, "0")}
</div>
</div>
{/* Content */}
<div className="flex-1 pb-2">
<div className="flex items-center gap-2 mb-2">
<span
className={`text-xs font-bold ${isA ? "text-[#B7A6FB]" : "text-cyan-400"}`}
>
Agent {label}
</span>
<span
className={`text-[9px] font-mono px-1.5 py-0.5 rounded border uppercase tracking-wide ${ACTION_BADGE[action] ?? ACTION_BADGE.propose}`}
>
<Icon name={ACTION_ICON[action] ?? "send"} className="text-[11px] align-middle mr-0.5" />
{ACTION_LABEL[action] ?? action}
</span>
<span className="text-[9px] text-slate-600 font-mono">{PERSONALITY_LABELS[personality]}</span>
</div>
<div
className={`rounded-lg p-3.5 text-sm backdrop-blur-sm border ${
isLast
? isA
? "bg-[#B7A6FB]/5 border-[#B7A6FB]/20"
: "bg-cyan-900/10 border-cyan-500/20"
: "bg-[rgba(7,3,18,0.4)] border-white/5"
}`}
>
{round.reasoning && (
<p className="text-slate-300 text-xs leading-relaxed font-light mb-2">
{round.reasoning}
</p>
)}
{round.proposal && typeof round.proposal === "object" && (
<ProposalSnippet proposal={round.proposal as Record<string, unknown>} />
)}
{/* Satisfaction */}
<div className="flex items-center gap-4 mt-2 pt-2 border-t border-white/5">
<div className="flex items-center gap-1.5">
<span className="text-[9px] text-slate-600 font-mono uppercase">Sat A</span>
<span className={`text-[10px] font-mono font-bold ${(round.satisfaction_a ?? 0) >= 70 ? "text-[#B7A6FB]" : "text-red-400"}`}>
{round.satisfaction_a?.toFixed(0) ?? "—"}%
</span>
</div>
<div className="flex items-center gap-1.5">
<span className="text-[9px] text-slate-600 font-mono uppercase">Sat B</span>
<span className={`text-[10px] font-mono font-bold ${(round.satisfaction_b ?? 0) >= 70 ? "text-cyan-400" : "text-red-400"}`}>
{round.satisfaction_b?.toFixed(0) ?? "—"}%
</span>
</div>
{round.concessions_made?.length > 0 && (
<span className="text-[9px] font-mono text-amber-400 border border-amber-500/20 bg-amber-500/10 px-1.5 py-0.5 rounded uppercase ml-auto">
Concession
</span>
)}
</div>
</div>
</div>
</div>
);
})}
</div>
);
}
function ProposalSnippet({ proposal }: { proposal: Record<string, unknown> }) {
const summary =
(proposal.summary as string) ?? (proposal.for_party_a as string) ?? null;
if (!summary) return null;
return (
<div className="code-snippet p-2 text-[10px] text-slate-400 leading-relaxed mt-1">
{summary}
</div>
);
}

View File

@@ -0,0 +1,184 @@
"use client";
import { Negotiation } from "@/lib/types";
import { FEATURE_LABELS } from "@/lib/utils";
function Icon({ name, className = "" }: { name: string; className?: string }) {
return <span className={`material-symbols-outlined ${className}`}>{name}</span>;
}
interface Props {
negotiation: Negotiation;
}
export default function ResolutionCard({ negotiation }: Props) {
const resolution = negotiation.resolution as Record<string, unknown> | null;
const status = negotiation.status;
const analytics = negotiation.analytics;
if (!resolution && status !== "resolved" && status !== "escalated") {
return (
<div className="text-xs text-slate-600 py-4 text-center font-mono">
Resolution not yet available
</div>
);
}
const final = (resolution?.final_proposal ?? {}) as Record<string, unknown>;
const details = (final.details ?? {}) as Record<string, unknown>;
const roundsTaken = resolution?.rounds_taken as number | undefined;
const summaryText = String(final.summary ?? resolution?.summary ?? "");
const forPartyA = final.for_party_a ? String(final.for_party_a) : null;
const forPartyB = final.for_party_b ? String(final.for_party_b) : null;
const upiLink = (details.upi_link ?? details.upilink)
? String(details.upi_link ?? details.upilink)
: null;
const isResolved = status === "resolved";
return (
<div className="space-y-5">
{/* Hero card — matches Stitch resolution screen */}
<div
className={`relative rounded-xl border overflow-hidden ${
isResolved
? "border-emerald-500/30 bg-emerald-900/5"
: "border-amber-500/30 bg-amber-900/5"
}`}
>
{/* Top gradient line */}
<div
className={`absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent ${
isResolved ? "via-emerald-400" : "via-amber-400"
} to-transparent opacity-60`}
/>
<div className="p-5">
<div className="flex items-center gap-3 mb-3">
<div
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full border text-[10px] font-bold uppercase tracking-wider ${
isResolved
? "bg-emerald-500/10 border-emerald-500/20 text-emerald-400"
: "bg-amber-500/10 border-amber-500/20 text-amber-400"
}`}
>
<Icon name={isResolved ? "check_circle" : "warning"} className="text-sm" />
{isResolved ? "Resolved" : "Escalated"}
</div>
<span className="text-[10px] text-slate-600 font-mono">
{FEATURE_LABELS[negotiation.feature_type]}
{roundsTaken ? ` · ${roundsTaken} round${roundsTaken > 1 ? "s" : ""}` : ""}
</span>
</div>
{summaryText && (
<p className="text-sm text-slate-300 leading-relaxed font-light">{summaryText}</p>
)}
</div>
</div>
{/* Stats row */}
{analytics && (
<div className="grid grid-cols-3 gap-3">
<MetricBox label="Fairness" value={`${analytics.fairness_score?.toFixed(0)}%`} highlight />
<MetricBox label="Total Rounds" value={String(roundsTaken ?? "—")} />
<MetricBox
label="Concessions"
value={`${analytics.total_concessions_a + analytics.total_concessions_b}`}
/>
</div>
)}
{/* Per-party outcomes */}
{(forPartyA || forPartyB) && (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{forPartyA && (
<div className="bg-[#B7A6FB]/5 border border-[#B7A6FB]/20 rounded-lg p-3 hover:border-[#B7A6FB]/40 transition-colors">
<div className="text-[10px] font-mono text-[#B7A6FB] mb-2 uppercase tracking-wider">Agent A</div>
<p className="text-xs text-slate-300 leading-relaxed">{forPartyA}</p>
</div>
)}
{forPartyB && (
<div className="bg-cyan-900/10 border border-cyan-500/20 rounded-lg p-3 hover:border-cyan-500/40 transition-colors">
<div className="text-[10px] font-mono text-cyan-400 mb-2 uppercase tracking-wider">Agent B</div>
<p className="text-xs text-slate-300 leading-relaxed">{forPartyB}</p>
</div>
)}
</div>
)}
{/* Action buttons */}
<div className="grid grid-cols-2 gap-3">
{upiLink && (
<a
href={upiLink}
target="_blank"
rel="noopener noreferrer"
className="group relative flex items-center gap-3 p-4 rounded-xl border border-[#222249] bg-[#101023] hover:border-[#B7A6FB]/40 transition-all overflow-hidden text-left"
>
<div className="p-2 rounded-lg bg-indigo-500/20 text-indigo-300 group-hover:bg-indigo-500/30 transition-colors">
<Icon name="credit_card" className="text-lg" />
</div>
<div>
<div className="text-white text-sm font-bold">Pay via UPI</div>
<div className="text-slate-500 text-[10px]">Instant settlement</div>
</div>
<Icon name="arrow_forward" className="ml-auto text-[#B7A6FB] text-base opacity-0 group-hover:opacity-100 transition-opacity" />
</a>
)}
<button className="group relative flex items-center gap-3 p-4 rounded-xl border border-[#222249] bg-[#101023] hover:border-[#B7A6FB]/40 transition-all overflow-hidden text-left">
<div className="p-2 rounded-lg bg-sky-500/20 text-sky-300 group-hover:bg-sky-500/30 transition-colors">
<Icon name="chat" className="text-lg" />
</div>
<div>
<div className="text-white text-sm font-bold">Telegram</div>
<div className="text-slate-500 text-[10px]">Open channel</div>
</div>
<Icon name="arrow_forward" className="ml-auto text-[#B7A6FB] text-base opacity-0 group-hover:opacity-100 transition-opacity" />
</button>
<button className="group relative flex items-center gap-3 p-4 rounded-xl border border-[#222249] bg-[#101023] hover:border-[#B7A6FB]/40 transition-all overflow-hidden text-left">
<div className="p-2 rounded-lg bg-orange-500/20 text-orange-300 group-hover:bg-orange-500/30 transition-colors">
<Icon name="picture_as_pdf" className="text-lg" />
</div>
<div>
<div className="text-white text-sm font-bold">Download PDF</div>
<div className="text-slate-500 text-[10px]">Full transcript</div>
</div>
<Icon name="download" className="ml-auto text-[#B7A6FB] text-base opacity-0 group-hover:opacity-100 transition-opacity" />
</button>
<button className="group relative flex items-center gap-3 p-4 rounded-xl border border-[#222249] bg-[#101023] hover:border-[#B7A6FB]/40 transition-all overflow-hidden text-left">
<div className="p-2 rounded-lg bg-pink-500/20 text-pink-300 group-hover:bg-pink-500/30 transition-colors">
<Icon name="graphic_eq" className="text-lg" />
</div>
<div>
<div className="text-white text-sm font-bold">Voice Summary</div>
<div className="text-slate-500 text-[10px]">AI generated audio</div>
</div>
<Icon name="play_arrow" className="ml-auto text-[#B7A6FB] text-base opacity-0 group-hover:opacity-100 transition-opacity" />
</button>
</div>
</div>
);
}
function MetricBox({
label,
value,
highlight = false,
}: {
label: string;
value: string;
highlight?: boolean;
}) {
return (
<div className="flex flex-col gap-1 p-3 rounded-lg bg-white/5 border border-white/10 hover:border-[#B7A6FB]/30 transition-colors">
<span className="text-slate-500 text-[10px] font-mono">{label}</span>
<span className={`text-xl font-light tabular-nums ${highlight ? "text-[#B7A6FB]" : "text-white"}`}>
{value}
</span>
</div>
);
}
interface Props {
negotiation: Negotiation;
}

View File

@@ -0,0 +1,96 @@
"use client";
import { SatisfactionPoint } from "@/lib/types";
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from "recharts";
interface Props {
data: SatisfactionPoint[];
}
export default function SatisfactionChart({ data }: Props) {
if (!data || data.length === 0) {
return (
<div className="flex items-center justify-center h-48 text-slate-600 text-xs font-mono">
No satisfaction data yet
</div>
);
}
return (
<div className="relative">
{/* Legend */}
<div className="flex gap-4 justify-end mb-3 text-[10px] font-mono">
<div className="flex items-center gap-1.5">
<span className="size-1.5 rounded-full bg-[#B7A6FB] shadow-[0_0_5px_#B7A6FB]" />
<span className="text-slate-400">Agent A</span>
</div>
<div className="flex items-center gap-1.5">
<span className="size-1.5 rounded-full bg-cyan-400 shadow-[0_0_5px_#22d3ee]" />
<span className="text-slate-400">Agent B</span>
</div>
</div>
<ResponsiveContainer width="100%" height={200}>
<LineChart data={data} margin={{ top: 4, right: 8, left: -20, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" stroke="rgba(255,255,255,0.04)" />
<XAxis
dataKey="round"
tick={{ fill: "#475569", fontSize: 10, fontFamily: "JetBrains Mono" }}
axisLine={{ stroke: "rgba(255,255,255,0.05)" }}
tickLine={false}
/>
<YAxis
domain={[0, 100]}
tick={{ fill: "#475569", fontSize: 10, fontFamily: "JetBrains Mono" }}
axisLine={false}
tickLine={false}
tickFormatter={(v) => `${v}%`}
/>
<Tooltip
contentStyle={{
background: "rgba(7, 3, 18, 0.9)",
border: "1px solid rgba(183, 166, 251, 0.2)",
borderRadius: 8,
backdropFilter: "blur(12px)",
}}
labelStyle={{ color: "#94a3b8", fontFamily: "JetBrains Mono", fontSize: 10 }}
itemStyle={{ color: "#e2e8f0", fontFamily: "JetBrains Mono", fontSize: 11 }}
formatter={(value: number | undefined) => [`${(value ?? 0).toFixed(0)}%`]}
/>
<Line
type="monotone"
dataKey="score_a"
stroke="#B7A6FB"
strokeWidth={1.5}
dot={{ fill: "#000", stroke: "#B7A6FB", strokeWidth: 1, r: 2 }}
activeDot={{ r: 4, fill: "#B7A6FB", stroke: "white", strokeWidth: 1 }}
name="Agent A"
/>
<Line
type="monotone"
dataKey="score_b"
stroke="#22d3ee"
strokeWidth={1.5}
dot={{ fill: "#000", stroke: "#22d3ee", strokeWidth: 1, r: 2 }}
activeDot={{ r: 4, fill: "#22d3ee", stroke: "white", strokeWidth: 1 }}
name="Agent B"
/>
</LineChart>
</ResponsiveContainer>
</div>
);
}
interface Props {
data: SatisfactionPoint[];
}

View File

@@ -0,0 +1,87 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
function Icon({ name, className = "" }: { name: string; className?: string }) {
return (
<span className={`material-symbols-outlined ${className}`}>{name}</span>
);
}
const NAV_ITEMS = [
{ icon: "dashboard", label: "Dashboard", href: "/dashboard" },
{ icon: "history", label: "History", href: "/history" },
{ icon: "analytics", label: "Analytics", href: "/analytics" },
{ icon: "settings", label: "Preferences", href: "/preferences" },
];
export default function Sidebar() {
const pathname = usePathname();
return (
<aside className="hidden md:flex w-60 flex-col bg-black/40 backdrop-blur-xl border-r border-white/5 relative z-20 shrink-0">
{/* Logo */}
<div className="flex h-16 items-center gap-3 px-5 border-b border-white/5">
<div className="flex items-center justify-center size-8 rounded-lg bg-[#B7A6FB]/10 border border-[#B7A6FB]/20 text-[#B7A6FB]">
<Icon name="hub" className="text-xl" />
</div>
<div>
<h1 className="text-white text-sm font-bold tracking-tight">
Agent<span className="text-[#B7A6FB] font-light">Mesh</span>
</h1>
<p className="text-[10px] text-slate-600 font-mono">v2.4.0</p>
</div>
</div>
{/* Nav */}
<nav className="flex flex-col gap-1 p-3 flex-1">
{NAV_ITEMS.map(({ icon, label, href }) => {
const isActive =
href === "/dashboard" ? pathname === "/dashboard" : pathname.startsWith(href);
return (
<Link
key={href}
href={href}
className={`group flex items-center gap-3 px-3 py-2 rounded-lg transition-all text-sm ${
isActive
? "bg-white/5 border border-white/10 text-white"
: "text-slate-500 hover:text-white hover:bg-white/5"
}`}
>
<Icon
name={icon}
className={`text-[18px] ${
isActive
? "text-[#B7A6FB]"
: "group-hover:text-[#B7A6FB] transition-colors"
}`}
/>
<span className="font-medium">{label}</span>
</Link>
);
})}
</nav>
{/* System status footer */}
<div className="p-3 border-t border-white/5">
<div className="rounded-lg bg-gradient-to-b from-white/5 to-transparent border border-white/5 p-3 relative overflow-hidden">
<div className="absolute -right-4 -top-4 w-16 h-16 bg-[#B7A6FB]/10 blur-2xl rounded-full" />
<div className="flex items-center gap-2 mb-2 relative z-10">
<Icon name="bolt" className="text-[#B7A6FB] text-base" />
<span className="text-[10px] font-bold text-white tracking-wide">
System Status
</span>
</div>
<div className="w-full bg-white/10 h-0.5 rounded-full mb-2 overflow-hidden">
<div className="bg-[#B7A6FB] h-full w-3/4 shadow-[0_0_8px_#B7A6FB]" />
</div>
<p className="text-[9px] text-slate-500 font-mono">
LATENCY: <span className="text-[#B7A6FB]">12ms</span> ·{" "}
<span className="text-emerald-400">OPERATIONAL</span>
</p>
</div>
</div>
</aside>
);
}