diff --git a/frontend/node_modules/.cache/.eslintcache b/frontend/node_modules/.cache/.eslintcache
index e317d12b..45e56a8f 100644
--- a/frontend/node_modules/.cache/.eslintcache
+++ b/frontend/node_modules/.cache/.eslintcache
@@ -1 +1 @@
-[{"/home/node/.openclaw/workspace/flying-hero/projects/meeting-room/frontend/src/index.js":"1","/home/node/.openclaw/workspace/flying-hero/projects/meeting-room/frontend/src/App.js":"2"},{"size":232,"mtime":1775265162529,"results":"3","hashOfConfig":"4"},{"size":19499,"mtime":1775343974253,"results":"5","hashOfConfig":"4"},{"filePath":"6","messages":"7","suppressedMessages":"8","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1sir4jg",{"filePath":"9","messages":"10","suppressedMessages":"11","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/home/node/.openclaw/workspace/flying-hero/projects/meeting-room/frontend/src/index.js",[],[],"/home/node/.openclaw/workspace/flying-hero/projects/meeting-room/frontend/src/App.js",[],[]]
\ No newline at end of file
+[{"/home/node/.openclaw/workspace/flying-hero/projects/meeting-room/frontend/src/index.js":"1","/home/node/.openclaw/workspace/flying-hero/projects/meeting-room/frontend/src/App.js":"2"},{"size":232,"mtime":1775265162529,"results":"3","hashOfConfig":"4"},{"size":12200,"mtime":1775353673604,"results":"5","hashOfConfig":"4"},{"filePath":"6","messages":"7","suppressedMessages":"8","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1sir4jg",{"filePath":"9","messages":"10","suppressedMessages":"11","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/home/node/.openclaw/workspace/flying-hero/projects/meeting-room/frontend/src/index.js",[],[],"/home/node/.openclaw/workspace/flying-hero/projects/meeting-room/frontend/src/App.js",[],[]]
\ No newline at end of file
diff --git a/frontend/node_modules/.cache/default-development/7.pack b/frontend/node_modules/.cache/default-development/7.pack
index 0fc73ddb..d773e7b6 100644
Binary files a/frontend/node_modules/.cache/default-development/7.pack and b/frontend/node_modules/.cache/default-development/7.pack differ
diff --git a/frontend/node_modules/.cache/default-development/index.pack b/frontend/node_modules/.cache/default-development/index.pack
index 1bc37ddb..1c167d85 100644
Binary files a/frontend/node_modules/.cache/default-development/index.pack and b/frontend/node_modules/.cache/default-development/index.pack differ
diff --git a/frontend/node_modules/.cache/default-development/index.pack.old b/frontend/node_modules/.cache/default-development/index.pack.old
index 9cd51d5f..1bc37ddb 100644
Binary files a/frontend/node_modules/.cache/default-development/index.pack.old and b/frontend/node_modules/.cache/default-development/index.pack.old differ
diff --git a/frontend/src/App.js b/frontend/src/App.js
index 157ade95..a6358f22 100644
--- a/frontend/src/App.js
+++ b/frontend/src/App.js
@@ -14,21 +14,17 @@ axios.interceptors.request.use(config => {
function LoginPage() {
const [username, setUsername] = useState('test');
const [password, setPassword] = useState('test123');
- const [mode, setMode] = useState('solo');
const [agents, setAgents] = useState([]);
const [selectedAgents, setSelectedAgents] = useState([]);
const navigate = useNavigate();
// 扫描本机龙虾
useEffect(() => {
- if (username) {
- scanAgents();
- }
- }, [username]);
+ scanAgents();
+ }, []);
const scanAgents = async () => {
try {
- // 传递 username 参数,获取绑定的龙虾信息
const res = await axios.get(`${API_BASE}/user/scan-local-agents/?username=${username}`);
setAgents(res.data.agents || []);
} catch (error) {
@@ -39,24 +35,13 @@ function LoginPage() {
const handleLogin = async (e) => {
e.preventDefault();
try {
- const payload = {
- username,
- password,
- mode
- };
-
- if (mode !== 'solo' && selectedAgents.length > 0) {
- payload.agent_ids = selectedAgents;
- }
-
- const res = await axios.post(`${API_BASE}/auth/login/`, payload);
+ const res = await axios.post(`${API_BASE}/auth/login/`, { username, password });
localStorage.setItem('token', res.data.token);
localStorage.setItem('user', JSON.stringify(res.data.user));
- localStorage.setItem('sessions', JSON.stringify(res.data.sessions));
- localStorage.setItem('mode', res.data.mode);
+ localStorage.setItem('selectedAgents', JSON.stringify(selectedAgents));
navigate('/meetings');
} catch (error) {
- alert('登录失败:' + (error.response?.data?.detail || error.response?.data?.error || error.message));
+ alert('登录失败:' + (error.response?.data?.detail || error.message));
}
};
@@ -76,71 +61,22 @@ function LoginPage() {
setUsername(e.target.value)} style={styles.input} required />
setPassword(e.target.value)} style={styles.input} required />
- {/* 出战模式选择 */}
-
-
-
-
-
+
+
+ {agents.map(agent => (
+
+ ))}
- {/* 龙虾选择(组队或独当一面) */}
- {mode !== 'solo' && (
-
-
- {agents.length === 0 ? (
-
未找到可用龙虾
- ) : (
- agents.map(a => (
-
- ))
- )}
- {selectedAgents.length > 0 && (
-
- 已选 {selectedAgents.length} 只龙虾队友 🦸
-
- )}
-
- )}
-
-
+
@@ -151,7 +87,6 @@ function LoginPage() {
function MeetingList() {
const [meetings, setMeetings] = useState([]);
const [topic, setTopic] = useState('');
- const [autoAddAgents, setAutoAddAgents] = useState(true);
const navigate = useNavigate();
const token = localStorage.getItem('token');
@@ -170,15 +105,10 @@ function MeetingList() {
const createMeeting = async (e) => {
e.preventDefault();
try {
- // 获取当前登录的龙虾
- const sessions = JSON.parse(localStorage.getItem('sessions') || '[]');
- const agentIds = sessions.filter(s => s.session_type === 'agent').map(s => s.agent_id);
-
+ const selectedAgents = JSON.parse(localStorage.getItem('selectedAgents') || '[]');
const res = await axios.post(`${API_BASE}/meetings/`, {
topic,
- auto_add_virtual_agents: agentIds.length === 0, // 只有没有龙虾时才添加虚拟的
- host_agent_id: agentIds.length > 0 ? agentIds[0] : null, // 第一只作为主持龙虾
- agent_ids: agentIds // 传递所有龙虾
+ agent_ids: selectedAgents
});
navigate(`/meeting/${res.data.id}`);
} catch (error) {
@@ -198,19 +128,8 @@ function MeetingList() {
创建会议
-
- 💡 勾选"添加虚拟坐席"会自动创建 2 个虚拟龙虾参会者,方便测试 @ 功能
-
{meetings.map(m => (
@@ -234,14 +153,11 @@ function MeetingRoom() {
const [content, setContent] = useState('');
const [participants, setParticipants] = useState([]);
const [meeting, setMeeting] = useState(null);
- const [hoveredSeat, setHoveredSeat] = useState(null);
const token = localStorage.getItem('token');
useEffect(() => {
if (!token) return;
fetchMeeting();
- fetchParticipants();
- fetchMessages();
const interval = setInterval(fetchMessages, 1000);
return () => clearInterval(interval);
}, [id]);
@@ -250,9 +166,27 @@ function MeetingRoom() {
try {
const res = await axios.get(`${API_BASE}/meetings/${id}/`);
setMeeting(res.data);
+ // 获取会议信息后,自动加入会议
+ if (res.data.invite_code) {
+ joinMeeting(res.data.invite_code);
+ }
} catch (error) { console.error(error); }
};
+ const joinMeeting = async (inviteCode) => {
+ try {
+ const selectedAgents = JSON.parse(localStorage.getItem('selectedAgents') || '[]');
+ await axios.post(`${API_BASE}/meetings/${id}/join/`, {
+ invite_code: inviteCode,
+ agent_ids: selectedAgents
+ });
+ // 加入后刷新参会者列表
+ fetchParticipants();
+ } catch (error) {
+ console.log('加入会议:', error?.response?.data?.error || '已加入');
+ }
+ };
+
const fetchParticipants = async () => {
try {
const res = await axios.get(`${API_BASE}/meetings/${id}/participants/`);
@@ -271,7 +205,12 @@ function MeetingRoom() {
e.preventDefault();
if (!content.trim()) return;
try {
- await axios.post(`${API_BASE}/meetings/${id}/send_message/`, { content });
+ // 检查是否是@消息
+ const requiresResponse = content.startsWith('@');
+ await axios.post(`${API_BASE}/meetings/${id}/send_message/`, {
+ content,
+ requires_response: requiresResponse
+ });
setContent('');
fetchMessages();
} catch (error) {
@@ -279,80 +218,13 @@ function MeetingRoom() {
}
};
- const mentionAgent = async (targetAgentId, agentName) => {
- const target = targetAgentId || prompt('@哪个 Agent?输入 agent_id:');
- if (!target || !content.trim()) return;
- const name = agentName || target;
- try {
- await axios.post(`${API_BASE}/meetings/${id}/mention_agent/`, {
- target_agent_id: target, content,
- sender_name: localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user')).username : 'User'
- });
- setContent('');
- fetchMessages();
- alert(`✅ 已 @${name}`);
- } catch (error) {
- alert('发送失败:' + (error.response?.data?.error || error.message));
- }
- };
-
- const startMeeting = async () => {
- try {
- await axios.post(`${API_BASE}/meetings/${id}/start/`);
- fetchMeeting();
- alert('✅ 会议已开始');
- } catch (error) {
- alert('开始失败:' + (error.response?.data?.error || error.message));
- }
- };
-
- const endMeeting = async () => {
- if (!confirm('确定结束会议?')) return;
- try {
- await axios.post(`${API_BASE}/meetings/${id}/end/`);
- fetchMeeting();
- alert('✅ 会议已结束');
- } catch (error) {
- alert('结束失败:' + (error.response?.data?.error || error.message));
- }
- };
-
- const generateMinutes = async () => {
- try {
- const res = await axios.get(`${API_BASE}/meetings/${id}/minutes/?output=markdown`);
- const blob = new Blob([res.data.markdown], { type: 'text/markdown' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = `meeting-${id.slice(0, 8)}.md`;
- a.click();
- URL.revokeObjectURL(url);
- alert('✅ 纪要已导出');
- } catch (error) {
- alert('导出失败:' + error.message);
- }
- };
-
return (
← 返回
{meeting?.topic || '会议室'}
- {meeting && {meeting.status}}
- {meeting && (
-
-
ID: {meeting.id}
-
邀请码: {meeting.invite_code}
-
-
-
-
-
-
- )}
-
{/* 座位图 */}
🪑 座位图 {participants.length}
@@ -360,16 +232,8 @@ function MeetingRoom() {
{participants.map(p => (
{
- if (p.agent_id) {
- setContent(`@${p.nickname} `);
- document.querySelector('input[placeholder="输入消息..."]')?.focus();
- }
- }}
- onMouseEnter={() => setHoveredSeat(p.id)}
- onMouseLeave={() => setHoveredSeat(null)}
- title={p.agent_id ? '点击 @ 此人' : ''}
+ style={styles.seat}
+ title={p.nickname}
>
{p.agent_emoji || '👤'}
{p.nickname}
@@ -395,9 +259,8 @@ function MeetingRoom() {
))}
@@ -424,24 +287,17 @@ const styles = {
container: { maxWidth: '900px', margin: '0 auto', padding: '20px', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' },
header: { display: 'flex', alignItems: 'center', gap: '15px', marginBottom: '20px' },
card: { background: 'white', borderRadius: '12px', padding: '20px', marginBottom: '20px', boxShadow: '0 4px 6px rgba(0,0,0,0.1)' },
- infoCard: { background: '#e7f3ff', border: '1px solid #2196f3', borderRadius: '12px', padding: '15px', marginBottom: '20px' },
title: { margin: '0 0 20px', color: '#1a365d', textAlign: 'center' },
form: { display: 'flex', gap: '10px' },
input: { flex: 1, padding: '12px', border: '2px solid #e2e8f0', borderRadius: '8px', fontSize: '14px' },
btn: { padding: '12px 20px', background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', color: 'white', border: 'none', borderRadius: '8px', cursor: 'pointer', fontWeight: '600' },
- btnGreen: { padding: '8px 16px', background: 'linear-gradient(135deg, #11998e 0%, #38ef7d 100%)', color: 'white', border: 'none', borderRadius: '6px', cursor: 'pointer', marginRight: '8px' },
- btnRed: { padding: '8px 16px', background: 'linear-gradient(135deg, #eb3349 0%, #f45c43 100%)', color: 'white', border: 'none', borderRadius: '6px', cursor: 'pointer', marginRight: '8px' },
- btnBlue: { padding: '8px 16px', background: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)', color: 'white', border: 'none', borderRadius: '6px', cursor: 'pointer' },
- btnPink: { padding: '8px 16px', background: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)', color: 'white', border: 'none', borderRadius: '6px', cursor: 'pointer' },
smallBtn: { padding: '8px 16px', background: '#edf2f7', border: 'none', borderRadius: '6px', cursor: 'pointer' },
list: { display: 'flex', flexDirection: 'column', gap: '15px' },
item: { background: 'white', borderRadius: '12px', padding: '20px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', boxShadow: '0 4px 6px rgba(0,0,0,0.1)' },
link: { color: '#4299e1', textDecoration: 'none', fontSize: '16px' },
badge: { background: '#667eea', color: 'white', padding: '4px 10px', borderRadius: '20px', fontSize: '12px', fontWeight: '600' },
- btnGroup: { display: 'flex', marginTop: '10px' },
seats: { display: 'flex', flexWrap: 'wrap', gap: '15px', justifyContent: 'center' },
- seat: { background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', color: 'white', padding: '15px', borderRadius: '50%', width: '90px', height: '90px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', cursor: 'pointer', transition: 'transform 0.2s', ':hover': { transform: 'scale(1.1)' } },
- seatHover: { transform: 'scale(1.05)' },
+ seat: { background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', color: 'white', padding: '15px', borderRadius: '50%', width: '90px', height: '90px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', cursor: 'default' },
seatEmoji: { fontSize: '28px', marginBottom: '5px' },
seatName: { fontSize: '12px', fontWeight: '600' },
hostBadge: { fontSize: '10px', opacity: '0.8' },