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() {

创建会议

setTopic(e.target.value)} style={styles.input} required /> -
-

- 💡 勾选"添加虚拟坐席"会自动创建 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() { ))}
- setContent(e.target.value)} style={styles.input} /> + setContent(e.target.value)} style={styles.input} /> -
@@ -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' },