// src/views/AgentChat/components/MeetingRoom/index.js // 投研会议室主组件 import React, { useRef, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Box, Flex, VStack, HStack, Text, Input, IconButton, Avatar, Badge, Spinner, Tooltip, Kbd, useColorModeValue, } from '@chakra-ui/react'; import { Send, Users, RefreshCw, MessageCircle, CheckCircle, AlertCircle, } from 'lucide-react'; import { MEETING_ROLES, MeetingStatus, getRoleConfig, } from '../../constants/meetingRoles'; import { useInvestmentMeeting } from '../../hooks/useInvestmentMeeting'; import MeetingMessageBubble from './MeetingMessageBubble'; import MeetingRolePanel from './MeetingRolePanel'; import MeetingWelcome from './MeetingWelcome'; /** * MeetingRoom - 投研会议室主组件 * * @param {Object} props * @param {Object} props.user - 当前用户信息 * @param {Function} props.onToast - Toast 通知函数 * @returns {JSX.Element} */ const MeetingRoom = ({ user, onToast }) => { const inputRef = useRef(null); const messagesEndRef = useRef(null); // 使用投研会议 Hook const { messages, status, speakingRoleId, currentRound, isConcluded, conclusion, inputValue, setInputValue, startMeeting, continueMeeting, sendUserMessage, resetMeeting, currentTopic, isLoading, } = useInvestmentMeeting({ userId: user?.id ? String(user.id) : 'anonymous', userNickname: user?.nickname || '匿名用户', onToast, }); // 自动滚动到底部 useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); // 处理键盘事件 const handleKeyPress = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } }; // 处理发送 const handleSend = () => { if (!inputValue.trim()) return; if (status === MeetingStatus.IDLE) { // 启动新会议 startMeeting(inputValue.trim()); setInputValue(''); } else if (status === MeetingStatus.CONCLUDED) { // 如果已结论,开始新会议 resetMeeting(); startMeeting(inputValue.trim()); setInputValue(''); } else if ( status === MeetingStatus.WAITING_INPUT || status === MeetingStatus.DISCUSSING || status === MeetingStatus.SPEAKING ) { // 用户可以在任何时候插话(包括讨论中和发言中) sendUserMessage(inputValue.trim()); setInputValue(''); } }; // 获取状态提示文字 const getStatusText = () => { switch (status) { case MeetingStatus.IDLE: return '请输入投研议题,开始会议讨论'; case MeetingStatus.STARTING: return '正在召集会议成员...'; case MeetingStatus.DISCUSSING: return `第 ${currentRound} 轮讨论进行中...`; case MeetingStatus.SPEAKING: const role = getRoleConfig(speakingRoleId); return `${role?.name || '成员'} 正在发言...`; case MeetingStatus.WAITING_INPUT: return '讨论暂停,您可以插话或等待继续'; case MeetingStatus.CONCLUDED: return '会议已结束,已得出投资建议'; case MeetingStatus.ERROR: return '会议出现异常,请重试'; default: return ''; } }; // 获取输入框占位符 const getPlaceholder = () => { if (status === MeetingStatus.IDLE) { return '输入投研议题,如:分析茅台最新财报...'; } else if (status === MeetingStatus.WAITING_INPUT) { return '输入您的观点参与讨论,或点击继续按钮...'; } else if (status === MeetingStatus.CONCLUDED) { return '会议已结束,输入新议题开始新会议...'; } else if (status === MeetingStatus.STARTING) { return '正在召集会议成员...'; } else if (status === MeetingStatus.DISCUSSING || status === MeetingStatus.SPEAKING) { return '随时输入您的观点参与讨论...'; } return '输入您的观点...'; }; return ( {/* 顶部标题栏 */} } bgGradient="linear(to-br, orange.400, red.500)" boxShadow="0 0 20px rgba(251, 146, 60, 0.5)" /> 投研会议室 {status === MeetingStatus.CONCLUDED ? ( ) : status === MeetingStatus.ERROR ? ( ) : ( )} {getStatusText()} {currentRound > 0 && ( 第 {currentRound} 轮 )} } onClick={resetMeeting} bg="rgba(255, 255, 255, 0.05)" color="gray.400" _hover={{ bg: 'rgba(255, 255, 255, 0.1)', color: 'white', }} /> {/* 主内容区 */} {/* 角色面板(左侧) */} {/* 消息区域(中间) */} {messages.length === 0 && status === MeetingStatus.IDLE ? ( { setInputValue(topic); inputRef.current?.focus(); }} /> ) : ( {/* 当前议题展示 */} {currentTopic && ( 📋 本次议题 {currentTopic} )} {/* 消息列表 */} {messages.map((message, index) => ( ))} {/* 正在发言指示器 */} {speakingRoleId && ( {getRoleConfig(speakingRoleId)?.name} 正在思考... )}
)} {/* 输入栏 */} setInputValue(e.target.value)} onKeyDown={handleKeyPress} placeholder={getPlaceholder()} isDisabled={status === MeetingStatus.STARTING} size="lg" bg="rgba(255, 255, 255, 0.05)" border="1px solid" borderColor="rgba(255, 255, 255, 0.1)" color="white" _placeholder={{ color: 'gray.500' }} _hover={{ borderColor: 'rgba(255, 255, 255, 0.2)', }} _focus={{ borderColor: 'orange.400', boxShadow: '0 0 0 1px var(--chakra-colors-orange-400)', }} /> : } onClick={handleSend} isDisabled={ !inputValue.trim() || status === MeetingStatus.STARTING } bgGradient="linear(to-r, orange.400, red.500)" color="white" _hover={{ bgGradient: 'linear(to-r, orange.500, red.600)', boxShadow: '0 8px 20px rgba(251, 146, 60, 0.4)', }} /> {/* 继续讨论按钮 */} {status === MeetingStatus.WAITING_INPUT && !isConcluded && ( } onClick={() => continueMeeting()} isDisabled={isLoading} bgGradient="linear(to-r, purple.400, blue.500)" color="white" _hover={{ bgGradient: 'linear(to-r, purple.500, blue.600)', }} /> )} Enter {status === MeetingStatus.IDLE ? '开始会议' : '发送消息'} {(status === MeetingStatus.WAITING_INPUT || status === MeetingStatus.DISCUSSING || status === MeetingStatus.SPEAKING) && ( 💡 随时输入观点参与讨论,您的发言会影响分析师的判断 )} ); }; export default MeetingRoom;