feat(governance): 新增 AI Agent 活動動畫
This commit is contained in:
@@ -0,0 +1,472 @@
|
||||
'use client'
|
||||
|
||||
import { BrainCircuit, Cpu, RadioTower, Route, ShieldCheck } from 'lucide-react'
|
||||
import type { CSSProperties } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { GlassCard } from '@/components/ui/glass-card'
|
||||
|
||||
type AgentActivityMode = 'automation' | 'market'
|
||||
type AgentActivityTone = 'ok' | 'warn' | 'danger' | 'neutral'
|
||||
|
||||
export type AgentActivityMetric = {
|
||||
label: string
|
||||
value: string | number
|
||||
tone?: AgentActivityTone
|
||||
}
|
||||
|
||||
export type AgentActivityConstellationProps = {
|
||||
mode: AgentActivityMode
|
||||
statusValue: string
|
||||
metrics: AgentActivityMetric[]
|
||||
}
|
||||
|
||||
function toneColor(tone: AgentActivityTone = 'neutral') {
|
||||
if (tone === 'ok') return '#22C55E'
|
||||
if (tone === 'warn') return '#F59E0B'
|
||||
if (tone === 'danger') return '#EF4444'
|
||||
return '#4A90D9'
|
||||
}
|
||||
|
||||
export function AgentActivityConstellation({
|
||||
mode,
|
||||
statusValue,
|
||||
metrics,
|
||||
}: AgentActivityConstellationProps) {
|
||||
const t = useTranslations('governance.agentActivity')
|
||||
const isMarket = mode === 'market'
|
||||
const title = isMarket ? t('marketTitle') : t('automationTitle')
|
||||
const subtitle = isMarket ? t('marketSubtitle') : t('automationSubtitle')
|
||||
|
||||
return (
|
||||
<GlassCard variant="subtle" padding="md" className="agent-activity-constellation min-w-0">
|
||||
<div className="agent-activity-layout">
|
||||
<div
|
||||
className={`agent-activity-stage ${isMarket ? 'is-market' : 'is-automation'}`}
|
||||
aria-label={title}
|
||||
role="img"
|
||||
>
|
||||
<div className="agent-activity-orbit agent-activity-orbit-outer" />
|
||||
<div className="agent-activity-orbit agent-activity-orbit-inner" />
|
||||
<div className="agent-activity-bus">
|
||||
<RadioTower size={18} strokeWidth={1.8} />
|
||||
</div>
|
||||
<div className="agent-activity-node agent-activity-node-openclaw">
|
||||
<ShieldCheck size={17} strokeWidth={1.9} />
|
||||
<span>OpenClaw</span>
|
||||
</div>
|
||||
<div className="agent-activity-node agent-activity-node-hermes">
|
||||
<BrainCircuit size={17} strokeWidth={1.9} />
|
||||
<span>Hermes</span>
|
||||
</div>
|
||||
<div className="agent-activity-node agent-activity-node-nemotron">
|
||||
<Cpu size={17} strokeWidth={1.9} />
|
||||
<span>NemoTron</span>
|
||||
</div>
|
||||
<div className="agent-activity-packet agent-activity-packet-a" />
|
||||
<div className="agent-activity-packet agent-activity-packet-b" />
|
||||
<div className="agent-activity-packet agent-activity-packet-c" />
|
||||
</div>
|
||||
|
||||
<div className="agent-activity-copy">
|
||||
<div className="agent-activity-heading">
|
||||
<Route size={14} strokeWidth={1.8} />
|
||||
<span>{title}</span>
|
||||
</div>
|
||||
<p>{subtitle}</p>
|
||||
<div className="agent-activity-status">
|
||||
<span>{t('status')}</span>
|
||||
<strong>{statusValue}</strong>
|
||||
</div>
|
||||
<div className="agent-activity-metrics">
|
||||
{metrics.map((metric) => (
|
||||
<div
|
||||
key={`${metric.label}-${metric.value}`}
|
||||
className="agent-activity-metric"
|
||||
style={{ '--metric-color': toneColor(metric.tone) } as CSSProperties}
|
||||
>
|
||||
<span>{metric.label}</span>
|
||||
<strong>{metric.value}</strong>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="agent-activity-footnote">{t('footnote')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style jsx>{`
|
||||
.agent-activity-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(220px, 0.82fr) minmax(0, 1.18fr);
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.agent-activity-stage {
|
||||
position: relative;
|
||||
min-height: 190px;
|
||||
border-radius: 8px;
|
||||
border: 0.5px solid rgba(20, 20, 19, 0.06);
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(90deg, rgba(20, 20, 19, 0.035) 1px, transparent 1px),
|
||||
linear-gradient(0deg, rgba(20, 20, 19, 0.035) 1px, transparent 1px),
|
||||
radial-gradient(circle at 50% 50%, rgba(74, 144, 217, 0.13), transparent 56%),
|
||||
#fbfaf5;
|
||||
background-size: 28px 28px, 28px 28px, auto, auto;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.agent-activity-stage::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -40%;
|
||||
background: conic-gradient(
|
||||
from 0deg,
|
||||
transparent 0deg,
|
||||
rgba(74, 144, 217, 0.2) 62deg,
|
||||
transparent 126deg,
|
||||
rgba(34, 197, 94, 0.18) 206deg,
|
||||
transparent 300deg
|
||||
);
|
||||
animation: agent-activity-sweep 9s linear infinite;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.agent-activity-stage.is-market::before {
|
||||
background: conic-gradient(
|
||||
from 0deg,
|
||||
transparent 0deg,
|
||||
rgba(245, 158, 11, 0.2) 62deg,
|
||||
transparent 142deg,
|
||||
rgba(74, 144, 217, 0.2) 230deg,
|
||||
transparent 300deg
|
||||
);
|
||||
}
|
||||
|
||||
.agent-activity-orbit {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(20, 20, 19, 0.08);
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.agent-activity-orbit-outer {
|
||||
width: 172px;
|
||||
height: 124px;
|
||||
border-style: dashed;
|
||||
animation: agent-activity-drift 12s linear infinite;
|
||||
}
|
||||
|
||||
.agent-activity-orbit-inner {
|
||||
width: 104px;
|
||||
height: 72px;
|
||||
border-color: rgba(74, 144, 217, 0.24);
|
||||
animation: agent-activity-drift-reverse 10s linear infinite;
|
||||
}
|
||||
|
||||
.agent-activity-bus {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 999px;
|
||||
color: #141413;
|
||||
background: rgba(255, 255, 255, 0.86);
|
||||
border: 0.5px solid rgba(20, 20, 19, 0.08);
|
||||
box-shadow: 0 12px 26px rgba(20, 20, 19, 0.08);
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.agent-activity-node {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
max-width: calc(100% - 24px);
|
||||
min-height: 30px;
|
||||
padding: 5px 8px;
|
||||
border-radius: 7px;
|
||||
border: 0.5px solid rgba(20, 20, 19, 0.08);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 10px 22px rgba(20, 20, 19, 0.06);
|
||||
color: #141413;
|
||||
font-family: 'DM Mono', monospace;
|
||||
font-size: 10px;
|
||||
line-height: 1.15;
|
||||
white-space: nowrap;
|
||||
animation: agent-activity-node-pulse 3.6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.agent-activity-node-openclaw {
|
||||
left: 16px;
|
||||
top: 20px;
|
||||
color: #4a90d9;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.agent-activity-node-hermes {
|
||||
right: 14px;
|
||||
top: 56px;
|
||||
color: #d97757;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
.agent-activity-node-nemotron {
|
||||
left: 50%;
|
||||
bottom: 18px;
|
||||
color: #22c55e;
|
||||
transform: translateX(-50%);
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
.agent-activity-packet {
|
||||
position: absolute;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 999px;
|
||||
background: #4a90d9;
|
||||
box-shadow: 0 0 18px rgba(74, 144, 217, 0.55);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.agent-activity-packet-a {
|
||||
animation: agent-activity-packet-a 4.8s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
||||
}
|
||||
|
||||
.agent-activity-packet-b {
|
||||
background: #d97757;
|
||||
box-shadow: 0 0 18px rgba(217, 119, 87, 0.5);
|
||||
animation: agent-activity-packet-b 5.4s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
||||
}
|
||||
|
||||
.agent-activity-packet-c {
|
||||
background: #22c55e;
|
||||
box-shadow: 0 0 18px rgba(34, 197, 94, 0.5);
|
||||
animation: agent-activity-packet-c 6s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
||||
}
|
||||
|
||||
.agent-activity-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.agent-activity-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
min-width: 0;
|
||||
color: #d97757;
|
||||
}
|
||||
|
||||
.agent-activity-heading span {
|
||||
color: #141413;
|
||||
font-family: Syne, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.agent-activity-copy p {
|
||||
margin: 0;
|
||||
color: #625f58;
|
||||
font-family: 'DM Mono', monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.55;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.agent-activity-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
color: #87867f;
|
||||
font-family: 'DM Mono', monospace;
|
||||
font-size: 10px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.agent-activity-status strong {
|
||||
color: #141413;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.agent-activity-metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.agent-activity-metric {
|
||||
min-width: 0;
|
||||
border-left: 2px solid var(--metric-color);
|
||||
padding: 7px 8px;
|
||||
border-radius: 6px;
|
||||
background: rgba(250, 249, 243, 0.74);
|
||||
}
|
||||
|
||||
.agent-activity-metric span,
|
||||
.agent-activity-footnote {
|
||||
display: block;
|
||||
color: #87867f;
|
||||
font-family: 'DM Mono', monospace;
|
||||
font-size: 9px;
|
||||
line-height: 1.3;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.agent-activity-metric strong {
|
||||
display: block;
|
||||
margin-top: 3px;
|
||||
color: #141413;
|
||||
font-family: 'DM Mono', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.2;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.agent-activity-footnote {
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
@keyframes agent-activity-sweep {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes agent-activity-drift {
|
||||
to {
|
||||
transform: translate(-50%, -50%) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes agent-activity-drift-reverse {
|
||||
to {
|
||||
transform: translate(-50%, -50%) rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes agent-activity-node-pulse {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 10px 22px rgba(20, 20, 19, 0.06);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 12px 30px rgba(74, 144, 217, 0.18);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes agent-activity-packet-a {
|
||||
0%,
|
||||
100% {
|
||||
left: 52px;
|
||||
top: 46px;
|
||||
opacity: 0.2;
|
||||
}
|
||||
42% {
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
opacity: 1;
|
||||
}
|
||||
72% {
|
||||
left: calc(100% - 58px);
|
||||
top: 76px;
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes agent-activity-packet-b {
|
||||
0%,
|
||||
100% {
|
||||
right: 58px;
|
||||
top: 82px;
|
||||
opacity: 0.18;
|
||||
}
|
||||
42% {
|
||||
right: 50%;
|
||||
top: 50%;
|
||||
opacity: 1;
|
||||
}
|
||||
72% {
|
||||
right: calc(50% - 6px);
|
||||
top: calc(100% - 45px);
|
||||
opacity: 0.78;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes agent-activity-packet-c {
|
||||
0%,
|
||||
100% {
|
||||
left: 50%;
|
||||
bottom: 38px;
|
||||
opacity: 0.18;
|
||||
}
|
||||
42% {
|
||||
left: calc(50% - 2px);
|
||||
bottom: 50%;
|
||||
opacity: 1;
|
||||
}
|
||||
72% {
|
||||
left: 54px;
|
||||
bottom: calc(100% - 54px);
|
||||
opacity: 0.72;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.agent-activity-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.agent-activity-stage {
|
||||
min-height: 176px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.agent-activity-metrics {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.agent-activity-node {
|
||||
font-size: 9px;
|
||||
padding: 5px 7px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.agent-activity-stage::before,
|
||||
.agent-activity-orbit,
|
||||
.agent-activity-node,
|
||||
.agent-activity-packet {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.agent-activity-packet {
|
||||
opacity: 0.65;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</GlassCard>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user