mirror of
https://github.com/arkorty/B.Tech-Project-III.git
synced 2026-04-19 20:51:49 +00:00
init
This commit is contained in:
161
negot8/dashboard/components/NegotiationTimeline.tsx
Normal file
161
negot8/dashboard/components/NegotiationTimeline.tsx
Normal 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'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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user