280 lines
9.2 KiB
TypeScript
280 lines
9.2 KiB
TypeScript
|
|
import { Shield, Zap } from "lucide-react";
|
|||
|
|
|
|||
|
|
type NodeState = "ready" | "waiting" | "pending";
|
|||
|
|
|
|||
|
|
interface BarrierNode {
|
|||
|
|
id: string;
|
|||
|
|
name: string;
|
|||
|
|
gradient: string;
|
|||
|
|
state: NodeState;
|
|||
|
|
waitingFor?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const nodes: BarrierNode[] = [
|
|||
|
|
{
|
|||
|
|
id: "n1",
|
|||
|
|
name: "CLA",
|
|||
|
|
gradient: "linear-gradient(135deg,#8b5cf6,#6366f1)",
|
|||
|
|
state: "ready",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: "n2",
|
|||
|
|
name: "KIM",
|
|||
|
|
gradient: "linear-gradient(135deg,#f59e0b,#d97706)",
|
|||
|
|
state: "waiting",
|
|||
|
|
waitingFor: "架构设计完成",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: "n3",
|
|||
|
|
name: "OPC",
|
|||
|
|
gradient: "linear-gradient(135deg,#10b981,#059669)",
|
|||
|
|
state: "ready",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: "n4",
|
|||
|
|
name: "USR",
|
|||
|
|
gradient: "linear-gradient(135deg,#f59e0b,#b45309)",
|
|||
|
|
state: "pending",
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const stateColors: Record<NodeState, string> = {
|
|||
|
|
ready: "#00ff9d",
|
|||
|
|
waiting: "#ff9500",
|
|||
|
|
pending: "#374151",
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const stateLabels: Record<NodeState, string> = {
|
|||
|
|
ready: "READY",
|
|||
|
|
waiting: "WAIT",
|
|||
|
|
pending: "IDLE",
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const syncPoints = [
|
|||
|
|
{ name: "INIT", completed: true },
|
|||
|
|
{ name: "REVIEW", completed: true },
|
|||
|
|
{ name: "DESIGN", completed: false, active: true },
|
|||
|
|
{ name: "IMPL", completed: false },
|
|||
|
|
{ name: "DEPLOY", completed: false },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
export function BarrierSyncCard() {
|
|||
|
|
const readyCount = nodes.filter(n => n.state === "ready").length;
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="glass-card" style={{ padding: 20, height: "100%", display: "flex", flexDirection: "column" }}>
|
|||
|
|
{/* Header */}
|
|||
|
|
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 14, flexShrink: 0 }}>
|
|||
|
|
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
|
|||
|
|
<div
|
|||
|
|
style={{
|
|||
|
|
width: 28,
|
|||
|
|
height: 28,
|
|||
|
|
borderRadius: 8,
|
|||
|
|
background: "rgba(0,240,255,0.1)",
|
|||
|
|
border: "1px solid rgba(0,240,255,0.2)",
|
|||
|
|
display: "flex",
|
|||
|
|
alignItems: "center",
|
|||
|
|
justifyContent: "center",
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
<Shield size={14} color="#00f0ff" />
|
|||
|
|
</div>
|
|||
|
|
<span className="card-title">栅栏同步</span>
|
|||
|
|
</div>
|
|||
|
|
<div style={{ display: "flex", alignItems: "center", gap: 6 }}>
|
|||
|
|
<span className="font-mono-code" style={{ fontSize: 11, color: "#ff9500" }}>
|
|||
|
|
{readyCount}/{nodes.length} 就绪
|
|||
|
|
</span>
|
|||
|
|
<div
|
|||
|
|
style={{
|
|||
|
|
padding: "3px 10px",
|
|||
|
|
background: "rgba(255,149,0,0.1)",
|
|||
|
|
border: "1px solid rgba(255,149,0,0.3)",
|
|||
|
|
borderRadius: 6,
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
<span className="font-mono-code" style={{ fontSize: 10, color: "#ff9500" }}>
|
|||
|
|
<span className="animate-pulse-fast">●</span> 等待触发
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div style={{ display: "flex", gap: 20, flex: 1, minHeight: 0 }}>
|
|||
|
|
{/* Left: Agent nodes */}
|
|||
|
|
<div style={{ display: "flex", flexDirection: "column", gap: 8, minWidth: 200 }}>
|
|||
|
|
{nodes.map(node => (
|
|||
|
|
<div
|
|||
|
|
key={node.id}
|
|||
|
|
style={{
|
|||
|
|
display: "flex",
|
|||
|
|
alignItems: "center",
|
|||
|
|
gap: 10,
|
|||
|
|
padding: "8px 12px",
|
|||
|
|
background: "rgba(0,0,0,0.3)",
|
|||
|
|
border: `1px solid ${stateColors[node.state]}30`,
|
|||
|
|
borderRadius: 10,
|
|||
|
|
transition: "all 0.3s ease",
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
<div
|
|||
|
|
className="agent-avatar"
|
|||
|
|
style={{ background: node.gradient, width: 32, height: 32, fontSize: 10 }}
|
|||
|
|
>
|
|||
|
|
{node.name}
|
|||
|
|
</div>
|
|||
|
|
<div style={{ flex: 1, minWidth: 0 }}>
|
|||
|
|
<div className="font-mono-code" style={{ fontSize: 11, color: "#9ca3af" }}>
|
|||
|
|
{node.name === "CLA" ? "Claude Code" : node.name === "KIM" ? "Kimi CLI" : node.name === "OPC" ? "OpenCode" : "Tech Lead"}
|
|||
|
|
</div>
|
|||
|
|
{node.waitingFor && (
|
|||
|
|
<div className="font-mono-code" style={{ fontSize: 9, color: "#ff9500", marginTop: 1 }}>
|
|||
|
|
等待: {node.waitingFor}
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
<div
|
|||
|
|
className="barrier-node"
|
|||
|
|
style={{
|
|||
|
|
width: 32,
|
|||
|
|
height: 32,
|
|||
|
|
borderColor: stateColors[node.state],
|
|||
|
|
color: stateColors[node.state],
|
|||
|
|
background: `${stateColors[node.state]}10`,
|
|||
|
|
fontSize: 8,
|
|||
|
|
}}
|
|||
|
|
title={stateLabels[node.state]}
|
|||
|
|
>
|
|||
|
|
{node.state === "ready" ? (
|
|||
|
|
<svg width="12" height="12" viewBox="0 0 12 12">
|
|||
|
|
<path d="M2 6L5 9L10 3" stroke="#00ff9d" strokeWidth="1.5" strokeLinecap="round" fill="none" />
|
|||
|
|
</svg>
|
|||
|
|
) : node.state === "waiting" ? (
|
|||
|
|
<Zap size={12} />
|
|||
|
|
) : (
|
|||
|
|
<span style={{ fontSize: 8 }}>—</span>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Right: Sync points */}
|
|||
|
|
<div style={{ flex: 1, minWidth: 0 }}>
|
|||
|
|
<div className="font-mono-code" style={{ fontSize: 10, color: "#6b7280", marginBottom: 10 }}>
|
|||
|
|
同步检查点
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Progress line */}
|
|||
|
|
<div style={{ position: "relative", marginBottom: 16 }}>
|
|||
|
|
<div
|
|||
|
|
style={{
|
|||
|
|
height: 3,
|
|||
|
|
background: "#111827",
|
|||
|
|
borderRadius: 2,
|
|||
|
|
position: "relative",
|
|||
|
|
overflow: "visible",
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
{/* Completed portion */}
|
|||
|
|
<div
|
|||
|
|
style={{
|
|||
|
|
position: "absolute",
|
|||
|
|
left: 0,
|
|||
|
|
top: 0,
|
|||
|
|
height: "100%",
|
|||
|
|
width: "42%",
|
|||
|
|
background: "linear-gradient(90deg,#00f0ff,#00ff9d)",
|
|||
|
|
borderRadius: 2,
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
{/* Active flowing portion */}
|
|||
|
|
<div
|
|||
|
|
style={{
|
|||
|
|
position: "absolute",
|
|||
|
|
left: "42%",
|
|||
|
|
top: 0,
|
|||
|
|
height: "100%",
|
|||
|
|
width: "16%",
|
|||
|
|
background: "linear-gradient(90deg,#ff9500,rgba(255,149,0,0.3))",
|
|||
|
|
borderRadius: 2,
|
|||
|
|
overflow: "hidden",
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
<div
|
|||
|
|
style={{
|
|||
|
|
position: "absolute",
|
|||
|
|
inset: 0,
|
|||
|
|
background: "linear-gradient(90deg,transparent,rgba(255,255,255,0.3),transparent)",
|
|||
|
|
animation: "line-flow 1.5s linear infinite",
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Dots */}
|
|||
|
|
{syncPoints.map((sp, i) => {
|
|||
|
|
const left = `${(i / (syncPoints.length - 1)) * 100}%`;
|
|||
|
|
return (
|
|||
|
|
<div
|
|||
|
|
key={sp.name}
|
|||
|
|
style={{
|
|||
|
|
position: "absolute",
|
|||
|
|
top: "50%",
|
|||
|
|
left,
|
|||
|
|
transform: "translate(-50%,-50%)",
|
|||
|
|
width: 12,
|
|||
|
|
height: 12,
|
|||
|
|
borderRadius: "50%",
|
|||
|
|
border: `2px solid ${sp.completed ? "#00ff9d" : sp.active ? "#ff9500" : "#374151"}`,
|
|||
|
|
background: sp.completed ? "#00ff9d" : sp.active ? "#ff9500" : "#111827",
|
|||
|
|
zIndex: 2,
|
|||
|
|
boxShadow: sp.active ? "0 0 8px rgba(255,149,0,0.6)" : sp.completed ? "0 0 6px rgba(0,255,157,0.4)" : "none",
|
|||
|
|
}}
|
|||
|
|
className={sp.active ? "animate-scale-pulse" : ""}
|
|||
|
|
/>
|
|||
|
|
);
|
|||
|
|
})}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Labels */}
|
|||
|
|
<div style={{ display: "flex", justifyContent: "space-between", marginTop: 12 }}>
|
|||
|
|
{syncPoints.map(sp => (
|
|||
|
|
<div
|
|||
|
|
key={sp.name}
|
|||
|
|
className="font-mono-code"
|
|||
|
|
style={{
|
|||
|
|
fontSize: 9,
|
|||
|
|
color: sp.completed ? "#00ff9d" : sp.active ? "#ff9500" : "#374151",
|
|||
|
|
textAlign: "center",
|
|||
|
|
flex: 1,
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
{sp.name}
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Status panel */}
|
|||
|
|
<div
|
|||
|
|
style={{
|
|||
|
|
padding: "10px 12px",
|
|||
|
|
background: "rgba(255,149,0,0.05)",
|
|||
|
|
border: "1px solid rgba(255,149,0,0.15)",
|
|||
|
|
borderRadius: 10,
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
<div className="font-mono-code" style={{ fontSize: 10, color: "#ff9500", marginBottom: 4 }}>
|
|||
|
|
⏸ 等待 DESIGN 检查点同步
|
|||
|
|
</div>
|
|||
|
|
<div className="font-mono-code" style={{ fontSize: 10, color: "#6b7280" }}>
|
|||
|
|
触发条件:所有 Agent 调用 wait_for_meeting("design_review") · 已就绪 {readyCount}/{nodes.length}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|