🔧 修改端口:避免与会议厅冲突
变更: - 前端:3000 → 4000 - 后端:8000 → 9000 原因: - 会议厅使用 3000/8000 - 避免端口冲突 新访问地址: - 前端:http://localhost:4000 - 后端 API: http://localhost:9000/api/
This commit is contained in:
@@ -104,3 +104,7 @@ REST_FRAMEWORK = {
|
||||
'rest_framework.permissions.AllowAny',
|
||||
]
|
||||
}
|
||||
|
||||
# 端口配置(避免与会议厅冲突)
|
||||
# 会议厅:3000/8000
|
||||
# 监控中心:4000/9000
|
||||
|
||||
485
code/frontend/src/components/MeetingRoom/index.js
Normal file
485
code/frontend/src/components/MeetingRoom/index.js
Normal file
@@ -0,0 +1,485 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const API_BASE = 'http://localhost:8000/api';
|
||||
|
||||
function MeetingRoom({ agents, onRefresh, onAgentToMeeting, onAgentFromMeeting }) {
|
||||
const [meetingAgents, setMeetingAgents] = useState([]);
|
||||
const [isMeeting, setIsMeeting] = useState(false);
|
||||
const [meetingTopic, setMeetingTopic] = useState('');
|
||||
const [meetingMinutes, setMeetingMinutes] = useState('');
|
||||
const [draggedAgent, setDraggedAgent] = useState(null);
|
||||
|
||||
// 拖拽进入会议室
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
};
|
||||
|
||||
// 拖拽离开会议室
|
||||
const handleDragLeave = () => {
|
||||
// 不做任何操作
|
||||
};
|
||||
|
||||
// 放入会议室
|
||||
const handleDrop = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const agentId = e.dataTransfer.getData('agentId');
|
||||
const agentName = e.dataTransfer.getData('agentName');
|
||||
const agentEmoji = e.dataTransfer.getData('agentEmoji');
|
||||
|
||||
if (agentId) {
|
||||
const agent = {
|
||||
id: parseInt(agentId),
|
||||
name: agentName,
|
||||
emoji: agentEmoji,
|
||||
seat: meetingAgents.length, // 自动分配座位
|
||||
};
|
||||
|
||||
// 添加到会议室
|
||||
if (!meetingAgents.find(a => a.id === agent.id)) {
|
||||
setMeetingAgents([...meetingAgents, agent]);
|
||||
|
||||
// 通知父组件
|
||||
if (onAgentToMeeting) {
|
||||
onAgentToMeeting(parseInt(agentId));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 从会议室拖出
|
||||
const handleSeatDragStart = (e, agent) => {
|
||||
e.dataTransfer.setData('agentId', agent.id.toString());
|
||||
e.dataTransfer.setData('agentName', agent.name);
|
||||
e.dataTransfer.setData('agentEmoji', agent.emoji);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
|
||||
// 从会议室移除
|
||||
setMeetingAgents(meetingAgents.filter(a => a.id !== agent.id));
|
||||
|
||||
// 通知父组件
|
||||
if (onAgentFromMeeting) {
|
||||
onAgentFromMeeting(agent.id);
|
||||
}
|
||||
};
|
||||
|
||||
// 开始会议
|
||||
const startMeeting = () => {
|
||||
if (meetingAgents.length === 0) {
|
||||
alert('🏛️ 请先拖入至少一只龙虾!');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsMeeting(true);
|
||||
|
||||
// 生成会议发言
|
||||
const speeches = meetingAgents.map(agent => {
|
||||
const speech = generateSpeech(agent);
|
||||
return `${agent.emoji} **${agent.name}**: ${speech}`;
|
||||
});
|
||||
|
||||
const minutes = `
|
||||
# 🏛️ 龙虾议事厅会议纪要
|
||||
|
||||
**时间**: ${new Date().toLocaleString('zh-CN')}
|
||||
**主题**: ${meetingTopic || '例行会议'}
|
||||
**参会**: ${meetingAgents.map(a => a.name).join('、')}
|
||||
|
||||
---
|
||||
|
||||
## 📝 发言记录
|
||||
|
||||
${speeches.join('\n\n')}
|
||||
|
||||
---
|
||||
|
||||
## ✅ 会议决议
|
||||
|
||||
待生成...
|
||||
|
||||
---
|
||||
|
||||
*Generated by Agent Diary* 🦀
|
||||
`;
|
||||
|
||||
setMeetingMinutes(minutes);
|
||||
};
|
||||
|
||||
// 生成发言(根据专长)
|
||||
const generateSpeech = (agent) => {
|
||||
const speeches = {
|
||||
'飞行侠': '作为主力,我会继续全力支持各项工作!大家有什么问题都可以找我!',
|
||||
'道童': '道德经云:道可道,非常道。我认为我们应该顺应自然,无为而治。',
|
||||
'墨子': '兼爱非攻!从技术角度来说,我建议优化架构,提高效率。',
|
||||
'织网者': '网站建设方面,我建议加强用户体验,优化界面设计。',
|
||||
'费曼': '从物理学角度看,我们应该找到最简单的解决方案。',
|
||||
'守望者': '监控数据显示,系统运行稳定。我会继续密切关注。',
|
||||
'白泽': '作为秘书,我会做好协调工作,确保信息畅通。',
|
||||
'谛听': '我收集到的情报显示,用户对我们的功能很满意!',
|
||||
};
|
||||
|
||||
return speeches[agent.name] || '我会尽力完成我的任务!';
|
||||
};
|
||||
|
||||
// 导出纪要
|
||||
const exportMinutes = () => {
|
||||
if (!meetingMinutes) {
|
||||
alert('📝 请先开始会议!');
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = new Blob([meetingMinutes], { type: 'text/markdown' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `会议纪要-${new Date().toISOString().split('T')[0]}.md`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
// 清空会议室
|
||||
const clearRoom = () => {
|
||||
setMeetingAgents([]);
|
||||
setIsMeeting(false);
|
||||
setMeetingMinutes('');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="meeting-room-container">
|
||||
<div className="meeting-header">
|
||||
<h2 className="room-title">🏛️ 议事厅 - 龙虾开会</h2>
|
||||
<div className="meeting-actions">
|
||||
<button
|
||||
className="action-btn start-btn"
|
||||
onClick={startMeeting}
|
||||
disabled={isMeeting || meetingAgents.length === 0}
|
||||
>
|
||||
{isMeeting ? '🎤 会议进行中' : '🔔 开始会议'}
|
||||
</button>
|
||||
<button
|
||||
className="action-btn export-btn"
|
||||
onClick={exportMinutes}
|
||||
disabled={!meetingMinutes}
|
||||
>
|
||||
📥 导出纪要
|
||||
</button>
|
||||
<button
|
||||
className="action-btn clear-btn"
|
||||
onClick={clearRoom}
|
||||
disabled={meetingAgents.length === 0}
|
||||
>
|
||||
🧹 清空会议室
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 会议室 */}
|
||||
<div
|
||||
className="meeting-room"
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
{/* 会议桌 */}
|
||||
<div className="conference-table">
|
||||
{/* 座位上的龙虾 */}
|
||||
<div className="seated-agents">
|
||||
{meetingAgents.map((agent, index) => (
|
||||
<div
|
||||
key={agent.id}
|
||||
className="seat"
|
||||
style={{
|
||||
transform: `rotate(${index * (360 / Math.max(meetingAgents.length, 1))}deg)`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="agent-on-seat"
|
||||
draggable
|
||||
onDragStart={(e) => handleSeatDragStart(e, agent)}
|
||||
title={agent.name}
|
||||
>
|
||||
<span className="agent-emoji">{agent.emoji}</span>
|
||||
<span className="agent-name">{agent.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 空状态提示 */}
|
||||
{meetingAgents.length === 0 && (
|
||||
<div className="room-empty-hint">
|
||||
<p className="hint-main">🪑 拖拽龙虾到会议室</p>
|
||||
<p className="hint-sub">从下方拖入龙虾,围坐开会</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 会议主题输入 */}
|
||||
{meetingAgents.length > 0 && !isMeeting && (
|
||||
<div className="topic-input">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="输入会议主题..."
|
||||
value={meetingTopic}
|
||||
onChange={(e) => setMeetingTopic(e.target.value)}
|
||||
className="topic-input-field"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 会议纪要 */}
|
||||
{meetingMinutes && (
|
||||
<div className="meeting-minutes">
|
||||
<h3 className="minutes-title">📝 会议纪要</h3>
|
||||
<div className="minutes-content">
|
||||
<pre>{meetingMinutes}</pre>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<style>{`
|
||||
.meeting-room-container {
|
||||
margin: 30px 0;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #f7fafc 0%, #edf2f7 100%);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.meeting-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.room-title {
|
||||
color: #1a365d;
|
||||
font-size: 1.5em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.meeting-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 0.95em;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.start-btn {
|
||||
background: linear-gradient(135deg, #48bb78 0%, #38a169 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.start-btn:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(72, 187, 120, 0.4);
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
background: linear-gradient(135deg, #4299e1 0%, #2b6cb0 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.export-btn:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(66, 153, 225, 0.4);
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
background: linear-gradient(135deg, #f56565 0%, #c53030 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.clear-btn:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(245, 101, 101, 0.4);
|
||||
}
|
||||
|
||||
.action-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.meeting-room {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
height: 400px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
background: linear-gradient(180deg, #faf8f5 0%, #edf2f7 100%);
|
||||
border-radius: 50%;
|
||||
border: 3px solid #d69e2e;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.conference-table {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.seated-agents {
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.seat {
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin-left: -50px;
|
||||
margin-top: -50px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.agent-on-seat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
cursor: grab;
|
||||
transition: all 0.3s;
|
||||
background: white;
|
||||
padding: 10px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.agent-on-seat:hover {
|
||||
transform: scale(1.1);
|
||||
z-index: 10;
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.agent-emoji {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.agent-name {
|
||||
font-size: 0.8em;
|
||||
font-weight: 600;
|
||||
color: #1a365d;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.room-empty-hint {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
color: #718096;
|
||||
}
|
||||
|
||||
.hint-main {
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.hint-sub {
|
||||
font-size: 1em;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.topic-input {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 80%;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.topic-input-field {
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
border: 2px solid #4299e1;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.topic-input-field:focus {
|
||||
outline: none;
|
||||
border-color: #2b6cb0;
|
||||
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.2);
|
||||
}
|
||||
|
||||
.meeting-minutes {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.minutes-title {
|
||||
color: #1a365d;
|
||||
font-size: 1.3em;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.minutes-content {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.minutes-content pre {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.9em;
|
||||
color: #2d3748;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.meeting-room {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.agent-emoji {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.agent-name {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
.meeting-actions {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MeetingRoom;
|
||||
306
code/frontend/src/pages/MeetingPage/index.js
Normal file
306
code/frontend/src/pages/MeetingPage/index.js
Normal file
@@ -0,0 +1,306 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import MeetingRoom from '../components/MeetingRoom';
|
||||
import axios from 'axios';
|
||||
|
||||
const API_BASE = 'http://localhost:8000/api';
|
||||
|
||||
function MeetingPage() {
|
||||
const navigate = useNavigate();
|
||||
const [agents, setAgents] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [meetingAgentIds, setMeetingAgentIds] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchAgents();
|
||||
}, []);
|
||||
|
||||
const fetchAgents = async () => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE}/agents/`);
|
||||
setAgents(response.data);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error('获取 Agent 失败:', error);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 龙虾放入会议室
|
||||
const handleAgentToMeeting = (agentId) => {
|
||||
if (!meetingAgentIds.includes(agentId)) {
|
||||
setMeetingAgentIds([...meetingAgentIds, agentId]);
|
||||
}
|
||||
};
|
||||
|
||||
// 龙虾从会议室拖出
|
||||
const handleAgentFromMeeting = (agentId) => {
|
||||
setMeetingAgentIds(meetingAgentIds.filter(id => id !== agentId));
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="meeting-page-loading">
|
||||
<div className="spinner"></div>
|
||||
<p>正在加载议事厅...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="meeting-page">
|
||||
<div className="page-header">
|
||||
<button onClick={() => navigate('/')} className="back-btn">
|
||||
← 返回监控中心
|
||||
</button>
|
||||
<h1>🏛️ 龙虾议事厅</h1>
|
||||
<div className="header-info">
|
||||
<span className="info-badge">📍 本地会议</span>
|
||||
<span className="info-badge">🦐 在线:{agents.length} 只</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="meeting-content">
|
||||
{/* 议事厅 */}
|
||||
<MeetingRoom
|
||||
agents={agents}
|
||||
onRefresh={fetchAgents}
|
||||
onAgentToMeeting={handleAgentToMeeting}
|
||||
onAgentFromMeeting={handleAgentFromMeeting}
|
||||
/>
|
||||
|
||||
{/* 待入座的龙虾 */}
|
||||
<div className="waiting-agents-section">
|
||||
<h2 className="section-title">
|
||||
🦐 待入座的龙虾 ({agents.filter(a => !meetingAgentIds.includes(a.id)).length})
|
||||
</h2>
|
||||
<div className="agent-grid">
|
||||
{agents
|
||||
.filter(agent => !meetingAgentIds.includes(agent.id))
|
||||
.map(agent => (
|
||||
<div
|
||||
key={agent.id}
|
||||
className="agent-card draggable-card"
|
||||
draggable
|
||||
onDragStart={(e) => {
|
||||
e.dataTransfer.setData('agentId', agent.id.toString());
|
||||
e.dataTransfer.setData('agentName', agent.name);
|
||||
e.dataTransfer.setData('agentEmoji', agent.emoji);
|
||||
e.target.style.opacity = '0.5';
|
||||
}}
|
||||
onDragEnd={(e) => {
|
||||
e.target.style.opacity = '1';
|
||||
}}
|
||||
title="拖拽我到会议室"
|
||||
>
|
||||
<div className="agent-header">
|
||||
<span className="agent-name">{agent.emoji} {agent.name}</span>
|
||||
<span className={`status status-${agent.status}`}>{agent.status}</span>
|
||||
</div>
|
||||
<div className="agent-info">
|
||||
<p>专长:{agent.specialty}</p>
|
||||
<p>端口:{agent.port}</p>
|
||||
</div>
|
||||
<div className="drag-hint">👆 拖我到会议室</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
.meeting-page {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
background: #edf2f7;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
color: #4a5568;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background: #e2e8f0;
|
||||
color: #2d3748;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
color: #1a365d;
|
||||
margin: 0;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.header-info {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.info-badge {
|
||||
background: #4299e1;
|
||||
color: white;
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.meeting-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.waiting-agents-section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
color: #1a365d;
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.agent-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.agent-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.agent-card.draggable-card {
|
||||
cursor: grab;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.agent-card.draggable-card:active {
|
||||
cursor: grabbing;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.agent-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.agent-name {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
color: #2d3748;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.85em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-healthy {
|
||||
background: #c6f6d5;
|
||||
color: #22543d;
|
||||
}
|
||||
|
||||
.status-warning {
|
||||
background: #feebc8;
|
||||
color: #744210;
|
||||
}
|
||||
|
||||
.status-error {
|
||||
background: #fed7d7;
|
||||
color: #742a2a;
|
||||
}
|
||||
|
||||
.agent-info {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.agent-info p {
|
||||
margin: 8px 0;
|
||||
color: #4a5568;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.drag-hint {
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px dashed #cbd5e0;
|
||||
color: #718096;
|
||||
font-size: 0.85em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.meeting-page-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
color: #718096;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 4px solid #e2e8f0;
|
||||
border-top-color: #4299e1;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.header-info {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.agent-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MeetingPage;
|
||||
Reference in New Issue
Block a user