// src/components/ChatBot/ChatInterface.js // 聊天界面主组件 import React, { useState, useRef, useEffect } from 'react'; import { Box, Flex, Input, IconButton, VStack, HStack, Text, Spinner, useColorModeValue, useToast, Divider, Badge, Menu, MenuButton, MenuList, MenuItem, Button, } from '@chakra-ui/react'; import { FiSend, FiRefreshCw, FiSettings, FiDownload } from 'react-icons/fi'; import { ChevronDownIcon } from '@chakra-ui/icons'; import MessageBubble from './MessageBubble'; import { mcpService } from '../../services/mcpService'; import { logger } from '../../utils/logger'; /** * 聊天界面组件 */ export const ChatInterface = () => { const [messages, setMessages] = useState([ { id: 1, content: '你好!我是AI投资助手,我可以帮你查询股票信息、新闻资讯、概念板块、涨停分析等。请问有什么可以帮到你的?', isUser: false, type: 'text', timestamp: new Date().toISOString(), }, ]); const [inputValue, setInputValue] = useState(''); const [isLoading, setIsLoading] = useState(false); const [availableTools, setAvailableTools] = useState([]); const messagesEndRef = useRef(null); const inputRef = useRef(null); const toast = useToast(); // 颜色主题 const bgColor = useColorModeValue('white', 'gray.800'); const borderColor = useColorModeValue('gray.200', 'gray.600'); const inputBg = useColorModeValue('gray.50', 'gray.700'); // 加载可用工具列表 useEffect(() => { const loadTools = async () => { const result = await mcpService.listTools(); if (result.success) { setAvailableTools(result.data); logger.info('ChatInterface', '已加载MCP工具', { count: result.data.length }); } }; loadTools(); }, []); // 自动滚动到底部 const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }; useEffect(() => { scrollToBottom(); }, [messages]); // 发送消息 const handleSendMessage = async () => { if (!inputValue.trim() || isLoading) return; const userMessage = { id: Date.now(), content: inputValue, isUser: true, type: 'text', timestamp: new Date().toISOString(), }; setMessages((prev) => [...prev, userMessage]); setInputValue(''); setIsLoading(true); try { // 调用MCP服务 const response = await mcpService.chat(inputValue, messages); let botMessage; if (response.success) { // 根据返回的数据类型构造消息 const data = response.data; if (typeof data === 'string') { botMessage = { id: Date.now() + 1, content: data, isUser: false, type: 'text', timestamp: new Date().toISOString(), }; } else if (Array.isArray(data)) { // 数据列表 botMessage = { id: Date.now() + 1, content: `找到 ${data.length} 条结果:`, isUser: false, type: 'data', data: data, timestamp: new Date().toISOString(), }; } else if (typeof data === 'object') { // 对象数据 botMessage = { id: Date.now() + 1, content: JSON.stringify(data, null, 2), isUser: false, type: 'markdown', timestamp: new Date().toISOString(), }; } else { botMessage = { id: Date.now() + 1, content: '抱歉,我无法理解这个查询结果。', isUser: false, type: 'text', timestamp: new Date().toISOString(), }; } } else { botMessage = { id: Date.now() + 1, content: `抱歉,查询失败:${response.error}`, isUser: false, type: 'text', timestamp: new Date().toISOString(), }; } setMessages((prev) => [...prev, botMessage]); } catch (error) { logger.error('ChatInterface', 'handleSendMessage', error); const errorMessage = { id: Date.now() + 1, content: `抱歉,发生了错误:${error.message}`, isUser: false, type: 'text', timestamp: new Date().toISOString(), }; setMessages((prev) => [...prev, errorMessage]); } finally { setIsLoading(false); inputRef.current?.focus(); } }; // 处理键盘事件 const handleKeyPress = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSendMessage(); } }; // 清空对话 const handleClearChat = () => { setMessages([ { id: 1, content: '对话已清空。有什么可以帮到你的?', isUser: false, type: 'text', timestamp: new Date().toISOString(), }, ]); }; // 复制消息 const handleCopyMessage = () => { toast({ title: '已复制', status: 'success', duration: 2000, isClosable: true, }); }; // 反馈 const handleFeedback = (type) => { logger.info('ChatInterface', 'Feedback', { type }); toast({ title: type === 'positive' ? '感谢反馈!' : '我们会改进', status: 'info', duration: 2000, isClosable: true, }); }; // 快捷问题 const quickQuestions = [ '查询贵州茅台的股票信息', '搜索人工智能相关新闻', '今日涨停股票有哪些', '新能源概念板块分析', ]; const handleQuickQuestion = (question) => { setInputValue(question); inputRef.current?.focus(); }; // 导出对话 const handleExportChat = () => { const chatText = messages .map((msg) => `[${msg.isUser ? '用户' : 'AI'}] ${msg.content}`) .join('\n\n'); const blob = new Blob([chatText], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `chat_${new Date().toISOString().slice(0, 10)}.txt`; a.click(); URL.revokeObjectURL(url); }; return ( {/* 头部工具栏 */} AI投资助手 在线 {availableTools.length > 0 && ( {availableTools.length} 个工具 )} } size="sm" variant="ghost" aria-label="清空对话" onClick={handleClearChat} /> } size="sm" variant="ghost" aria-label="导出对话" onClick={handleExportChat} /> } size="sm" variant="ghost" aria-label="设置" /> 模型设置 快捷指令 历史记录 {/* 消息列表 */} {messages.map((message) => ( ))} {isLoading && ( AI正在思考... )}
{/* 快捷问题(仅在消息较少时显示) */} {messages.length <= 2 && ( 快捷问题: {quickQuestions.map((question, idx) => ( ))} )} {/* 输入框 */} setInputValue(e.target.value)} onKeyPress={handleKeyPress} placeholder="输入消息... (Shift+Enter换行,Enter发送)" bg={inputBg} border="none" _focus={{ boxShadow: 'none' }} mr={2} disabled={isLoading} /> } colorScheme="blue" aria-label="发送" onClick={handleSendMessage} isLoading={isLoading} disabled={!inputValue.trim()} /> ); }; export default ChatInterface;