feat: 初始化多Agent协作系统项目并添加直播回放功能
- 添加导航栏组件及直播回放按钮 - 实现视频播放模态框 - 配置赛博朋克风格主题 - 添加课程首页和课程页面 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
359
src/components/AgentNetworkBackground.tsx
Normal file
359
src/components/AgentNetworkBackground.tsx
Normal file
@@ -0,0 +1,359 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
|
||||
// 网络节点组件 - 代表Agent
|
||||
const NetworkNode: React.FC<{
|
||||
id: number;
|
||||
onConnect: (id: number) => void;
|
||||
connections: number[];
|
||||
}> = ({ id, onConnect, connections }) => {
|
||||
const [position] = useState({
|
||||
x: Math.random() * 90 + 5,
|
||||
y: Math.random() * 90 + 5,
|
||||
size: Math.random() * 8 + 4,
|
||||
});
|
||||
|
||||
const [isActive, setIsActive] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
if (Math.random() < 0.3) {
|
||||
setIsActive(true);
|
||||
onConnect(id);
|
||||
setTimeout(() => setIsActive(false), 2000);
|
||||
}
|
||||
}, Math.random() * 8000 + 5000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [id, onConnect]);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="absolute"
|
||||
style={{
|
||||
left: `${position.x}%`,
|
||||
top: `${position.y}%`,
|
||||
width: `${position.size}px`,
|
||||
height: `${position.size}px`,
|
||||
}}
|
||||
initial={{ opacity: 0, scale: 0 }}
|
||||
animate={{
|
||||
opacity: isActive ? 0.9 : 0.4,
|
||||
scale: isActive ? 1.5 : 1,
|
||||
}}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
opacity: { duration: 0.3 }
|
||||
}}
|
||||
>
|
||||
{/* 节点核心 */}
|
||||
<div
|
||||
className={`w-full h-full rounded-full transition-all duration-500 ${
|
||||
isActive
|
||||
? 'bg-gradient-to-r from-cyber-pink-400 to-neon-purple-400 shadow-lg'
|
||||
: 'bg-gradient-to-r from-cyber-pink-500/50 to-neon-purple-500/50'
|
||||
}`}
|
||||
style={{
|
||||
boxShadow: isActive
|
||||
? `0 0 ${position.size * 2}px rgba(6, 182, 212, 0.6), 0 0 ${position.size * 4}px rgba(16, 185, 129, 0.3)`
|
||||
: `0 0 ${position.size}px rgba(6, 182, 212, 0.2)`
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 活跃时的脉冲效果 */}
|
||||
{isActive && (
|
||||
<motion.div
|
||||
className="absolute inset-0 rounded-full border-2 border-neon-cyan-300"
|
||||
initial={{ scale: 1, opacity: 0.6 }}
|
||||
animate={{ scale: 3, opacity: 0 }}
|
||||
transition={{ duration: 1.5, ease: "easeOut" }}
|
||||
/>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
// 数据流粒子组件
|
||||
const DataParticle: React.FC<{ path: { start: {x: number, y: number}, end: {x: number, y: number} } }> = ({ path }) => {
|
||||
const [isVisible, setIsVisible] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setIsVisible(false), 3000);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(path.end.x - path.start.x, 2) + Math.pow(path.end.y - path.start.y, 2)
|
||||
);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="absolute w-2 h-2 bg-neon-purple-400 rounded-full"
|
||||
style={{
|
||||
left: `${path.start.x}%`,
|
||||
top: `${path.start.y}%`,
|
||||
boxShadow: '0 0 8px rgba(16, 185, 129, 0.8)'
|
||||
}}
|
||||
initial={{
|
||||
x: 0,
|
||||
y: 0,
|
||||
opacity: 0,
|
||||
scale: 0.5
|
||||
}}
|
||||
animate={{
|
||||
x: `${(path.end.x - path.start.x) * (100/100)}%`,
|
||||
y: `${(path.end.y - path.start.y) * (100/100)}%`,
|
||||
opacity: [0, 1, 1, 0],
|
||||
scale: [0.5, 1, 1, 0.5]
|
||||
}}
|
||||
transition={{
|
||||
duration: Math.max(distance / 20, 2),
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// 连接线组件
|
||||
const ConnectionLine: React.FC<{
|
||||
start: {x: number, y: number};
|
||||
end: {x: number, y: number};
|
||||
isActive: boolean;
|
||||
}> = ({ start, end, isActive }) => {
|
||||
const length = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2));
|
||||
const angle = Math.atan2(end.y - start.y, end.x - start.x) * 180 / Math.PI;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="absolute origin-left"
|
||||
style={{
|
||||
left: `${start.x}%`,
|
||||
top: `${start.y}%`,
|
||||
width: `${length}%`,
|
||||
height: '1px',
|
||||
transform: `rotate(${angle}deg)`,
|
||||
background: isActive
|
||||
? 'linear-gradient(90deg, rgba(6, 182, 212, 0.8), rgba(16, 185, 129, 0.8))'
|
||||
: 'linear-gradient(90deg, rgba(6, 182, 212, 0.2), rgba(16, 185, 129, 0.2))',
|
||||
boxShadow: isActive ? '0 0 4px rgba(6, 182, 212, 0.5)' : 'none'
|
||||
}}
|
||||
initial={{ scaleX: 0, opacity: 0 }}
|
||||
animate={{
|
||||
scaleX: 1,
|
||||
opacity: isActive ? 0.8 : 0.3
|
||||
}}
|
||||
exit={{ scaleX: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// 主背景组件
|
||||
const AgentNetworkBackground: React.FC = () => {
|
||||
const [activeConnections, setActiveConnections] = useState<{
|
||||
start: {x: number, y: number};
|
||||
end: {x: number, y: number};
|
||||
id: string;
|
||||
}[]>([]);
|
||||
const [dataParticles, setDataParticles] = useState<{
|
||||
path: { start: {x: number, y: number}, end: {x: number, y: number} };
|
||||
id: string;
|
||||
}[]>([]);
|
||||
|
||||
const nodePositions = React.useRef<{[key: number]: {x: number, y: number}}>({});
|
||||
|
||||
// 初始化节点位置
|
||||
React.useEffect(() => {
|
||||
for (let i = 0; i < 12; i++) {
|
||||
nodePositions.current[i] = {
|
||||
x: Math.random() * 90 + 5,
|
||||
y: Math.random() * 90 + 5
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleNodeConnect = (nodeId: number) => {
|
||||
// 随机选择其他节点连接
|
||||
const otherNodes = Object.keys(nodePositions.current)
|
||||
.map(Number)
|
||||
.filter(id => id !== nodeId);
|
||||
|
||||
if (otherNodes.length === 0) return;
|
||||
|
||||
const targetId = otherNodes[Math.floor(Math.random() * otherNodes.length)];
|
||||
const start = nodePositions.current[nodeId];
|
||||
const end = nodePositions.current[targetId];
|
||||
|
||||
if (!start || !end) return;
|
||||
|
||||
const connectionId = `${nodeId}-${targetId}-${Date.now()}`;
|
||||
|
||||
// 添加连接线
|
||||
const newConnection = { start, end, id: connectionId };
|
||||
setActiveConnections(prev => [...prev, newConnection]);
|
||||
|
||||
// 添加数据粒子
|
||||
const particleId = `particle-${Date.now()}`;
|
||||
setDataParticles(prev => [...prev, { path: { start, end }, id: particleId }]);
|
||||
|
||||
// 清除连接线
|
||||
setTimeout(() => {
|
||||
setActiveConnections(prev => prev.filter(conn => conn.id !== connectionId));
|
||||
}, 3000);
|
||||
|
||||
// 清除数据粒子
|
||||
setTimeout(() => {
|
||||
setDataParticles(prev => prev.filter(p => p.id !== particleId));
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 pointer-events-none overflow-hidden z-0">
|
||||
{/* 基础深色技术背景 */}
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background: `
|
||||
radial-gradient(circle at 25% 25%, rgba(15, 23, 42, 0.8) 0%, transparent 50%),
|
||||
radial-gradient(circle at 75% 75%, rgba(8, 145, 178, 0.1) 0%, transparent 50%),
|
||||
linear-gradient(135deg,
|
||||
#0f172a 0%,
|
||||
#1e293b 25%,
|
||||
#0f172a 50%,
|
||||
#164e63 75%,
|
||||
#0f172a 100%
|
||||
)
|
||||
`
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 网格背景 - 体现技术感 */}
|
||||
<div
|
||||
className="absolute inset-0 opacity-[0.08]"
|
||||
style={{
|
||||
backgroundImage: `
|
||||
linear-gradient(rgba(6, 182, 212, 0.3) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(6, 182, 212, 0.3) 1px, transparent 1px)
|
||||
`,
|
||||
backgroundSize: '50px 50px'
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 动态光晕效果 */}
|
||||
<motion.div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background: `
|
||||
radial-gradient(600px circle at var(--mouse-x, 50%) var(--mouse-y, 50%),
|
||||
rgba(6, 182, 212, 0.1) 0%,
|
||||
rgba(16, 185, 129, 0.05) 40%,
|
||||
transparent 100%
|
||||
)
|
||||
`
|
||||
}}
|
||||
animate={{
|
||||
'--mouse-x': ['30%', '70%', '30%'],
|
||||
'--mouse-y': ['40%', '60%', '40%']
|
||||
} as any}
|
||||
transition={{
|
||||
duration: 20,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 数据流背景效果 */}
|
||||
<motion.div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background: `
|
||||
linear-gradient(45deg,
|
||||
transparent 0%,
|
||||
rgba(6, 182, 212, 0.05) 25%,
|
||||
transparent 50%,
|
||||
rgba(16, 185, 129, 0.05) 75%,
|
||||
transparent 100%
|
||||
)
|
||||
`,
|
||||
transform: 'translateX(-100%)'
|
||||
}}
|
||||
animate={{
|
||||
transform: ['translateX(-100%)', 'translateX(100%)']
|
||||
}}
|
||||
transition={{
|
||||
duration: 15,
|
||||
repeat: Infinity,
|
||||
ease: "linear"
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 连接线层 */}
|
||||
<div className="absolute inset-0">
|
||||
<AnimatePresence>
|
||||
{activeConnections.map(connection => (
|
||||
<ConnectionLine
|
||||
key={connection.id}
|
||||
start={connection.start}
|
||||
end={connection.end}
|
||||
isActive={true}
|
||||
/>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* 网络节点 - Agent */}
|
||||
<div className="absolute inset-0">
|
||||
{Array.from({ length: 12 }, (_, i) => (
|
||||
<NetworkNode
|
||||
key={i}
|
||||
id={i}
|
||||
onConnect={handleNodeConnect}
|
||||
connections={[]}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 数据粒子层 */}
|
||||
<div className="absolute inset-0">
|
||||
<AnimatePresence>
|
||||
{dataParticles.map(particle => (
|
||||
<DataParticle
|
||||
key={particle.id}
|
||||
path={particle.path}
|
||||
/>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* 顶部渐变遮罩 - 确保文字可读性 */}
|
||||
<div
|
||||
className="absolute inset-0 pointer-events-none"
|
||||
style={{
|
||||
background: `
|
||||
linear-gradient(180deg,
|
||||
rgba(15, 23, 42, 0.3) 0%,
|
||||
transparent 20%,
|
||||
transparent 80%,
|
||||
rgba(15, 23, 42, 0.2) 100%
|
||||
)
|
||||
`
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 性能优化 */}
|
||||
<style>{`
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentNetworkBackground;
|
||||
Reference in New Issue
Block a user