feat(web): enrich OpenClaw live ops room animation
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 24s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 24s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
This commit is contained in:
@@ -1357,6 +1357,11 @@
|
||||
"marker": "marker",
|
||||
"updated": "updated"
|
||||
},
|
||||
"animation": {
|
||||
"loop": "loop",
|
||||
"on": "on",
|
||||
"off": "off"
|
||||
},
|
||||
"panels": {
|
||||
"rollups": "Scene metrics",
|
||||
"boundaries": "Safety boundaries",
|
||||
|
||||
@@ -1357,6 +1357,11 @@
|
||||
"marker": "部署 marker",
|
||||
"updated": "更新"
|
||||
},
|
||||
"animation": {
|
||||
"loop": "動畫迴圈",
|
||||
"on": "開啟",
|
||||
"off": "關閉"
|
||||
},
|
||||
"panels": {
|
||||
"rollups": "場景指標",
|
||||
"boundaries": "安全邊界",
|
||||
|
||||
@@ -190,6 +190,11 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
"host_or_k8s_write_performed",
|
||||
].every((key) => scene.boundaries[key] === false);
|
||||
}, [scene]);
|
||||
const packetSlots = useMemo(() => {
|
||||
const count = Math.max(8, Math.min(14, scene?.rollups.animated_entity_count ?? 10));
|
||||
return Array.from({ length: count }, (_, index) => index);
|
||||
}, [scene]);
|
||||
const animationLoopEnabled = scene?.room.animation_loop.enabled === true;
|
||||
|
||||
return (
|
||||
<AppLayout locale={params.locale} fullBleed>
|
||||
@@ -238,16 +243,53 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative aspect-[16/10] min-h-[360px] overflow-hidden bg-[#eef3f8] sm:min-h-[500px]">
|
||||
<div className="absolute inset-x-[5%] bottom-[7%] top-[8%] rotate-[-2deg] skew-y-[-7deg] border border-[#c8d6df] bg-[#fdfefe] shadow-[0_28px_70px_rgba(54,72,88,0.16)]" />
|
||||
<div className="absolute inset-x-[7%] bottom-[11%] top-[12%] rotate-[-2deg] skew-y-[-7deg] bg-[linear-gradient(90deg,rgba(74,144,217,0.12)_1px,transparent_1px),linear-gradient(0deg,rgba(74,144,217,0.1)_1px,transparent_1px)] bg-[size:42px_42px]" />
|
||||
<div
|
||||
data-testid="openclaw-live-ops-room"
|
||||
className="relative aspect-[16/10] min-h-[420px] overflow-hidden bg-[#e8eff3] sm:min-h-[560px]"
|
||||
>
|
||||
<div className="absolute inset-x-[3%] bottom-[4%] top-[7%] rotate-[-2deg] skew-y-[-7deg] border border-[#b7c8d0] bg-[#fdfefe] shadow-[0_30px_80px_rgba(37,55,68,0.18)]" />
|
||||
<div className="absolute inset-x-[5%] bottom-[9%] top-[11%] rotate-[-2deg] skew-y-[-7deg] bg-[linear-gradient(90deg,rgba(74,144,217,0.14)_1px,transparent_1px),linear-gradient(0deg,rgba(74,144,217,0.11)_1px,transparent_1px)] bg-[size:44px_44px]" />
|
||||
<div className="absolute left-[8%] top-[10%] h-[13%] w-[84%] rotate-[-2deg] skew-y-[-7deg] border border-[#c6d1d5] bg-[#e9eee8]" />
|
||||
<div className="absolute left-[14%] top-[14%] h-10 w-[17%] rotate-[-2deg] skew-y-[-7deg] border border-[#aebbc0] bg-[#26333a] shadow-[0_10px_22px_rgba(37,55,68,0.22)]">
|
||||
<span className="openclaw-screen-sweep absolute inset-0" />
|
||||
<span className="absolute left-2 top-2 h-1.5 w-14 bg-[#70d6a4]" />
|
||||
<span className="absolute bottom-2 left-2 h-1.5 w-9 bg-[#f0bd69]" />
|
||||
</div>
|
||||
<div className="absolute right-[14%] top-[14%] h-10 w-[17%] rotate-[-2deg] skew-y-[-7deg] border border-[#aebbc0] bg-[#26333a] shadow-[0_10px_22px_rgba(37,55,68,0.22)]">
|
||||
<span className="openclaw-screen-sweep absolute inset-0" />
|
||||
<span className="absolute left-2 top-2 h-1.5 w-11 bg-[#8fc29a]" />
|
||||
<span className="absolute bottom-2 left-2 h-1.5 w-16 bg-[#4a90d9]" />
|
||||
</div>
|
||||
|
||||
<div className="absolute left-1/2 top-[48%] h-[19%] w-[30%] -translate-x-1/2 -translate-y-1/2 rotate-[-2deg] skew-y-[-7deg] border border-[#b7b0a5] bg-[#ead9bd] shadow-[0_22px_42px_rgba(63,49,34,0.16)]" />
|
||||
<div className="absolute left-1/2 top-[46%] h-[10%] w-[20%] -translate-x-1/2 -translate-y-1/2 rotate-[-2deg] skew-y-[-7deg] border border-[#87939a] bg-[#27333a] shadow-[0_14px_28px_rgba(37,55,68,0.2)]">
|
||||
<span className="absolute left-3 top-3 h-1.5 w-16 bg-[#4a90d9]" />
|
||||
<span className="absolute bottom-3 right-3 h-1.5 w-12 bg-[#8fc29a]" />
|
||||
</div>
|
||||
<div className="absolute left-[25%] top-[77%] h-[9%] w-[15%] rotate-[-2deg] skew-y-[-7deg] border border-[#b7b0a5] bg-[#d6c8aa]" />
|
||||
<div className="absolute right-[20%] top-[76%] h-[10%] w-[12%] rotate-[-2deg] skew-y-[-7deg] border border-[#b7b0a5] bg-[#d8e5df]" />
|
||||
|
||||
{packetSlots.map((slot) => (
|
||||
<span
|
||||
key={slot}
|
||||
data-testid="openclaw-flow-packet"
|
||||
className="openclaw-flow-packet absolute h-2.5 w-2.5 border border-[#f0bd69] bg-[#fff6c7] shadow-[0_0_18px_rgba(240,189,105,0.5)]"
|
||||
style={{
|
||||
left: `${14 + (slot % 7) * 11}%`,
|
||||
top: `${28 + (slot % 4) * 12}%`,
|
||||
animationDelay: `${slot * 0.34}s`,
|
||||
animationDuration: `${5.2 + (slot % 4) * 0.5}s`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{zones.map((zone) => {
|
||||
const Icon = zoneIconByKind[zone.kind] ?? Activity;
|
||||
return (
|
||||
<div
|
||||
key={zone.zone_id}
|
||||
className="absolute flex h-14 w-28 -translate-x-1/2 -translate-y-1/2 rotate-[-2deg] items-center gap-2 border border-[#cbd4d8] bg-white/90 px-2 shadow-[0_10px_24px_rgba(54,72,88,0.12)] backdrop-blur"
|
||||
data-testid="openclaw-zone"
|
||||
className="absolute flex h-14 w-28 -translate-x-1/2 -translate-y-1/2 rotate-[-2deg] items-center gap-2 border border-[#cbd4d8] bg-white/95 px-2 shadow-[0_10px_24px_rgba(54,72,88,0.12)] backdrop-blur"
|
||||
style={{ left: `${zone.position.x}%`, top: `${zone.position.y}%` }}
|
||||
>
|
||||
<span className="flex h-8 w-8 shrink-0 items-center justify-center border border-[#d8d3c7] bg-[#f7f8f7] text-[#4a90d9]">
|
||||
@@ -263,6 +305,7 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
{workItems.map((item, index) => (
|
||||
<div
|
||||
key={item.work_item_id}
|
||||
data-testid="openclaw-work-token"
|
||||
className={cn(
|
||||
"openclaw-work-token absolute h-8 w-20 -translate-x-1/2 -translate-y-1/2 border px-2 py-1 text-[10px] font-semibold shadow-[0_8px_20px_rgba(54,72,88,0.16)]",
|
||||
stateClass(stateFromWorkItemStatus(item.status)),
|
||||
@@ -280,6 +323,7 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
{agents.map((agent, index) => (
|
||||
<div
|
||||
key={agent.agent_id}
|
||||
data-testid="openclaw-agent-avatar"
|
||||
className="openclaw-agent absolute -translate-x-1/2 -translate-y-1/2"
|
||||
style={{
|
||||
left: `${agent.position.x}%`,
|
||||
@@ -289,10 +333,22 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-14 w-14 items-center justify-center border-2 bg-white shadow-[0_16px_30px_rgba(54,72,88,0.2)]",
|
||||
"relative flex h-14 w-14 items-center justify-center border-2 bg-white shadow-[0_16px_30px_rgba(54,72,88,0.2)]",
|
||||
stateClass(agent.state),
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"absolute -right-1 -top-1 h-3 w-3 border border-white",
|
||||
agent.state === "blocked"
|
||||
? "bg-[#d95f4f]"
|
||||
: agent.state === "verified"
|
||||
? "bg-[#4f9b62]"
|
||||
: agent.state === "working"
|
||||
? "bg-[#4a90d9]"
|
||||
: "bg-[#f0bd69]",
|
||||
)}
|
||||
/>
|
||||
<Bot className="h-6 w-6" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="mt-1 max-w-28 truncate border border-[#d8d3c7] bg-white px-1.5 py-0.5 text-center text-[10px] font-semibold text-[#34302a] shadow-sm">
|
||||
@@ -306,6 +362,19 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
<span className="border border-[#d8d3c7] bg-white/90 px-2 py-1 font-mono text-[11px] text-[#5f5b52]">
|
||||
{t("source.marker")} {shortValue(scene?.source.deploy_readback_marker)}
|
||||
</span>
|
||||
<span
|
||||
data-testid="openclaw-animation-loop"
|
||||
className={cn(
|
||||
"border px-2 py-1 font-mono text-[11px]",
|
||||
animationLoopEnabled
|
||||
? "border-[#8fc29a] bg-[#f0faf2] text-[#17602a]"
|
||||
: "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]",
|
||||
)}
|
||||
>
|
||||
{t("animation.loop")}{" "}
|
||||
{animationLoopEnabled ? t("animation.on") : t("animation.off")} ·{" "}
|
||||
{scene?.room.animation_loop.tick_ms ?? 0}ms
|
||||
</span>
|
||||
<span className="border border-[#d8d3c7] bg-white/90 px-2 py-1 font-mono text-[11px] text-[#5f5b52]">
|
||||
{t("source.updated")}{" "}
|
||||
{updatedAt
|
||||
@@ -328,7 +397,7 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
</h2>
|
||||
<Activity className="h-4 w-4 text-[#4a90d9]" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="mt-4 grid grid-cols-2 gap-2">
|
||||
<div className="mt-4 grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||
{[
|
||||
["agents", scene?.rollups.agent_count],
|
||||
["workItems", scene?.rollups.work_item_count],
|
||||
@@ -454,9 +523,55 @@ export default function OpenClawLiveOpsSpacePage({
|
||||
animation: openclaw-work-token-flow 6.2s ease-in-out infinite;
|
||||
will-change: transform;
|
||||
}
|
||||
@keyframes openclaw-flow-packet {
|
||||
0% {
|
||||
transform: translate3d(-8px, 10px, 0) scale(0.76);
|
||||
opacity: 0;
|
||||
}
|
||||
18% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
transform: translate3d(22px, -14px, 0) scale(1);
|
||||
opacity: 0.86;
|
||||
}
|
||||
100% {
|
||||
transform: translate3d(48px, -28px, 0) scale(0.72);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes openclaw-screen-sweep {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
30% {
|
||||
opacity: 0.42;
|
||||
}
|
||||
100% {
|
||||
transform: translateX(160%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.openclaw-flow-packet {
|
||||
animation: openclaw-flow-packet 5.8s ease-in-out infinite;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
.openclaw-screen-sweep {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(112, 214, 164, 0.3),
|
||||
transparent
|
||||
);
|
||||
animation: openclaw-screen-sweep 3.8s linear infinite;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.openclaw-agent,
|
||||
.openclaw-work-token {
|
||||
.openclaw-work-token,
|
||||
.openclaw-flow-packet,
|
||||
.openclaw-screen-sweep {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user