diff --git a/src/views/AgentChat/index.js b/src/views/AgentChat/index.js index 5bb0ebd5..29d7097c 100644 --- a/src/views/AgentChat/index.js +++ b/src/views/AgentChat/index.js @@ -1,53 +1,857 @@ -// src/views/AgentChat/index.js -// Agent聊天页面 +// src/views/AgentChat/index_v3.js +// Agent聊天页面 V3 - 带左侧会话列表和用户信息集成 -import React from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { Box, - Container, - Heading, - Text, + Flex, VStack, + HStack, + Text, + Input, + IconButton, + Button, + Avatar, + Heading, + Divider, + Spinner, + Badge, useColorModeValue, + useToast, + Progress, + Fade, + Collapse, + useDisclosure, + InputGroup, + InputLeftElement, + Menu, + MenuButton, + MenuList, + MenuItem, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalBody, + ModalCloseButton, + Tooltip, } from '@chakra-ui/react'; -import { ChatInterfaceV2 } from '../../components/ChatBot'; +import { + FiSend, + FiSearch, + FiPlus, + FiMessageSquare, + FiTrash2, + FiMoreVertical, + FiRefreshCw, + FiDownload, + FiCpu, + FiUser, + FiZap, + FiClock, +} from 'react-icons/fi'; +import { useAuth } from '@contexts/AuthContext'; +import { PlanCard } from '@components/ChatBot/PlanCard'; +import { StepResultCard } from '@components/ChatBot/StepResultCard'; +import { logger } from '@utils/logger'; +import axios from 'axios'; /** - * Agent聊天页面 - * 提供基于MCP的AI助手对话功能 + * Agent消息类型 */ -const AgentChat = () => { +const MessageTypes = { + USER: 'user', + AGENT_THINKING: 'agent_thinking', + AGENT_PLAN: 'agent_plan', + AGENT_EXECUTING: 'agent_executing', + AGENT_RESPONSE: 'agent_response', + ERROR: 'error', +}; + +/** + * Agent聊天页面 V3 + */ +const AgentChatV3 = () => { + const { user } = useAuth(); // 获取当前用户信息 + const toast = useToast(); + + // 会话相关状态 + const [sessions, setSessions] = useState([]); + const [currentSessionId, setCurrentSessionId] = useState(null); + const [isLoadingSessions, setIsLoadingSessions] = useState(true); + + // 消息相关状态 + const [messages, setMessages] = useState([]); + const [inputValue, setInputValue] = useState(''); + const [isProcessing, setIsProcessing] = useState(false); + const [currentProgress, setCurrentProgress] = useState(0); + + // UI 状态 + const [searchQuery, setSearchQuery] = useState(''); + const { isOpen: isSidebarOpen, onToggle: toggleSidebar } = useDisclosure({ defaultIsOpen: true }); + + // Refs + const messagesEndRef = useRef(null); + const inputRef = useRef(null); + + // 颜色主题 const bgColor = useColorModeValue('gray.50', 'gray.900'); - const cardBg = useColorModeValue('white', 'gray.800'); + const sidebarBg = useColorModeValue('white', 'gray.800'); + const chatBg = useColorModeValue('white', 'gray.800'); + const inputBg = useColorModeValue('white', 'gray.700'); + const borderColor = useColorModeValue('gray.200', 'gray.600'); + const hoverBg = useColorModeValue('gray.100', 'gray.700'); + const activeBg = useColorModeValue('blue.50', 'blue.900'); + const userBubbleBg = useColorModeValue('blue.500', 'blue.600'); + const agentBubbleBg = useColorModeValue('white', 'gray.700'); + + // ==================== 会话管理函数 ==================== + + // 加载会话列表 + const loadSessions = async () => { + if (!user?.id) return; + + setIsLoadingSessions(true); + try { + const response = await axios.get('/mcp/agent/sessions', { + params: { user_id: user.id, limit: 50 }, + }); + + if (response.data.success) { + setSessions(response.data.data); + logger.info('会话列表加载成功', response.data.data); + } + } catch (error) { + logger.error('加载会话列表失败', error); + toast({ + title: '加载失败', + description: '无法加载会话列表', + status: 'error', + duration: 3000, + }); + } finally { + setIsLoadingSessions(false); + } + }; + + // 加载会话历史 + const loadSessionHistory = async (sessionId) => { + if (!sessionId) return; + + try { + const response = await axios.get(`/mcp/agent/history/${sessionId}`, { + params: { limit: 100 }, + }); + + if (response.data.success) { + const history = response.data.data; + + // 将历史记录转换为消息格式 + const formattedMessages = history.map((msg, idx) => ({ + id: `${sessionId}-${idx}`, + type: msg.message_type === 'user' ? MessageTypes.USER : MessageTypes.AGENT_RESPONSE, + content: msg.message, + plan: msg.plan ? JSON.parse(msg.plan) : null, + stepResults: msg.steps ? JSON.parse(msg.steps) : null, + timestamp: msg.timestamp, + })); + + setMessages(formattedMessages); + logger.info('会话历史加载成功', formattedMessages); + } + } catch (error) { + logger.error('加载会话历史失败', error); + toast({ + title: '加载失败', + description: '无法加载会话历史', + status: 'error', + duration: 3000, + }); + } + }; + + // 创建新会话 + const createNewSession = () => { + setCurrentSessionId(null); + setMessages([ + { + id: Date.now(), + type: MessageTypes.AGENT_RESPONSE, + content: `你好${user?.nickname || ''}!我是价小前,北京价值前沿科技公司的AI投研助手。\n\n我会通过多步骤分析来帮助你深入了解金融市场。\n\n你可以问我:\n• 全面分析某只股票\n• 某个行业的投资机会\n• 今日市场热点\n• 某个概念板块的表现`, + timestamp: new Date().toISOString(), + }, + ]); + }; + + // 切换会话 + const switchSession = (sessionId) => { + setCurrentSessionId(sessionId); + loadSessionHistory(sessionId); + }; + + // 删除会话(需要后端API支持) + const deleteSession = async (sessionId) => { + // TODO: 实现删除会话的后端API + toast({ + title: '删除会话', + description: '此功能尚未实现', + status: 'info', + duration: 2000, + }); + }; + + // ==================== 消息处理函数 ==================== + + // 自动滚动到底部 + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + // 添加消息 + const addMessage = (message) => { + setMessages((prev) => [...prev, { ...message, id: Date.now() }]); + }; + + // 发送消息 + const handleSendMessage = async () => { + if (!inputValue.trim() || isProcessing) return; + + // 权限检查 + if (user?.id !== 'max') { + toast({ + title: '权限不足', + description: '「价小前投研」功能目前仅对特定用户开放。如需使用,请联系管理员。', + status: 'warning', + duration: 5000, + isClosable: true, + }); + return; + } + + const userMessage = { + type: MessageTypes.USER, + content: inputValue, + timestamp: new Date().toISOString(), + }; + + addMessage(userMessage); + const userInput = inputValue; + setInputValue(''); + setIsProcessing(true); + setCurrentProgress(0); + + let currentPlan = null; + let stepResults = []; + + try { + // 1. 显示思考状态 + addMessage({ + type: MessageTypes.AGENT_THINKING, + content: '正在分析你的问题...', + timestamp: new Date().toISOString(), + }); + + setCurrentProgress(10); + + // 2. 调用后端API(非流式) + const response = await axios.post('/mcp/agent/chat', { + message: userInput, + conversation_history: messages + .filter((m) => m.type === MessageTypes.USER || m.type === MessageTypes.AGENT_RESPONSE) + .map((m) => ({ + isUser: m.type === MessageTypes.USER, + content: m.content, + })), + user_id: user?.id || 'anonymous', + user_nickname: user?.nickname || '匿名用户', + user_avatar: user?.avatar || '', + session_id: currentSessionId, + }); + + // 移除思考消息 + setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_THINKING)); + + if (response.data.success) { + const data = response.data; + + // 更新 session_id(如果是新会话) + if (data.session_id && !currentSessionId) { + setCurrentSessionId(data.session_id); + } + + // 显示执行计划 + if (data.plan) { + currentPlan = data.plan; + addMessage({ + type: MessageTypes.AGENT_PLAN, + content: '已制定执行计划', + plan: data.plan, + timestamp: new Date().toISOString(), + }); + setCurrentProgress(30); + } + + // 显示执行步骤 + if (data.steps && data.steps.length > 0) { + stepResults = data.steps; + addMessage({ + type: MessageTypes.AGENT_EXECUTING, + content: '正在执行步骤...', + plan: currentPlan, + stepResults: stepResults, + timestamp: new Date().toISOString(), + }); + setCurrentProgress(70); + } + + // 移除执行中消息 + setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_EXECUTING)); + + // 显示最终结果 + addMessage({ + type: MessageTypes.AGENT_RESPONSE, + content: data.final_answer || data.message || '处理完成', + plan: currentPlan, + stepResults: stepResults, + metadata: data.metadata, + timestamp: new Date().toISOString(), + }); + + setCurrentProgress(100); + + // 重新加载会话列表 + loadSessions(); + } + } catch (error) { + logger.error('Agent chat error', error); + + // 移除思考/执行中消息 + setMessages((prev) => + prev.filter( + (m) => m.type !== MessageTypes.AGENT_THINKING && m.type !== MessageTypes.AGENT_EXECUTING + ) + ); + + const errorMessage = error.response?.data?.error || error.message || '处理失败'; + + addMessage({ + type: MessageTypes.ERROR, + content: `处理失败:${errorMessage}`, + timestamp: new Date().toISOString(), + }); + + toast({ + title: '处理失败', + description: errorMessage, + status: 'error', + duration: 5000, + isClosable: true, + }); + } finally { + setIsProcessing(false); + setCurrentProgress(0); + inputRef.current?.focus(); + } + }; + + // 处理键盘事件 + const handleKeyPress = (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSendMessage(); + } + }; + + // 清空对话 + const handleClearChat = () => { + createNewSession(); + }; + + // 导出对话 + const handleExportChat = () => { + const chatText = messages + .filter((m) => m.type === MessageTypes.USER || m.type === MessageTypes.AGENT_RESPONSE) + .map((msg) => `[${msg.type === MessageTypes.USER ? '用户' : '价小前'}] ${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); + }; + + // ==================== 初始化 ==================== + + useEffect(() => { + if (user) { + loadSessions(); + createNewSession(); + } + }, [user]); + + // ==================== 渲染 ==================== + + // 快捷问题 + const quickQuestions = [ + '全面分析贵州茅台这只股票', + '今日涨停股票有哪些亮点', + '新能源概念板块的投资机会', + '半导体行业最新动态', + ]; + + // 筛选会话 + const filteredSessions = sessions.filter( + (session) => + !searchQuery || + session.last_message?.toLowerCase().includes(searchQuery.toLowerCase()) + ); return ( - - - - {/* 页面标题 */} - - AI投资助手 - - 基于MCP协议的智能投资顾问,支持股票查询、新闻搜索、概念分析等多种功能 - + + {/* 左侧会话列表 */} + + + {/* 侧边栏头部 */} + + + + {/* 搜索框 */} + + + + + setSearchQuery(e.target.value)} + /> + - {/* 聊天界面 */} - - + {isLoadingSessions ? ( + + + + ) : filteredSessions.length === 0 ? ( + + + + {searchQuery ? '没有找到匹配的对话' : '暂无对话记录'} + + + ) : ( + filteredSessions.map((session) => ( + switchSession(session.session_id)} + > + + + + {session.last_message || '新对话'} + + + + + {new Date(session.last_timestamp).toLocaleDateString('zh-CN', { + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + })} + + + {session.message_count} 条 + + + + + + } + size="xs" + variant="ghost" + onClick={(e) => e.stopPropagation()} + /> + + } + color="red.500" + onClick={(e) => { + e.stopPropagation(); + deleteSession(session.session_id); + }} + > + 删除对话 + + + + + + )) + )} + + + {/* 用户信息 */} + + + + + + {user?.nickname || '未登录'} + + + {user?.id || 'anonymous'} + + + - - - + + + + {/* 主聊天区域 */} + + {/* 聊天头部 */} + + + + } + size="sm" + variant="ghost" + aria-label="切换侧边栏" + onClick={toggleSidebar} + /> + } /> + + 价小前投研 + + + + + 智能分析 + + + + 多步骤深度研究 + + + + + + + } + size="sm" + variant="ghost" + aria-label="清空对话" + onClick={handleClearChat} + /> + } + size="sm" + variant="ghost" + aria-label="导出对话" + onClick={handleExportChat} + /> + + + + {/* 进度条 */} + {isProcessing && ( + + )} + + + {/* 消息列表 */} + + + {messages.map((message) => ( + + + + ))} +
+ + + + {/* 快捷问题 */} + {messages.length <= 2 && !isProcessing && ( + + + 💡 试试这些问题: + + + {quickQuestions.map((question, idx) => ( + + ))} + + + )} + + {/* 输入框 */} + + + setInputValue(e.target.value)} + onKeyPress={handleKeyPress} + placeholder="输入你的问题,我会进行深度分析..." + bg={inputBg} + border="1px" + borderColor={borderColor} + _focus={{ borderColor: 'blue.500', boxShadow: '0 0 0 1px #3182CE' }} + mr={2} + disabled={isProcessing} + size="lg" + /> + : } + colorScheme="blue" + aria-label="发送" + onClick={handleSendMessage} + isLoading={isProcessing} + disabled={!inputValue.trim() || isProcessing} + size="lg" + /> + + + + ); }; -export default AgentChat; +/** + * 消息渲染器 + */ +const MessageRenderer = ({ message, userAvatar }) => { + const userBubbleBg = useColorModeValue('blue.500', 'blue.600'); + const agentBubbleBg = useColorModeValue('white', 'gray.700'); + const borderColor = useColorModeValue('gray.200', 'gray.600'); + + switch (message.type) { + case MessageTypes.USER: + return ( + + + + + {message.content} + + + } /> + + + ); + + case MessageTypes.AGENT_THINKING: + return ( + + + } /> + + + + + {message.content} + + + + + + ); + + case MessageTypes.AGENT_PLAN: + return ( + + + } /> + + + + + + ); + + case MessageTypes.AGENT_EXECUTING: + return ( + + + } /> + + + {message.stepResults?.map((result, idx) => ( + + ))} + + + + ); + + case MessageTypes.AGENT_RESPONSE: + return ( + + + } /> + + {/* 最终总结 */} + + + {message.content} + + + {/* 元数据 */} + {message.metadata && ( + + 总步骤: {message.metadata.total_steps} + ✓ {message.metadata.successful_steps} + {message.metadata.failed_steps > 0 && ( + ✗ {message.metadata.failed_steps} + )} + 耗时: {message.metadata.total_execution_time?.toFixed(1)}s + + )} + + + {/* 执行详情(可选) */} + {message.plan && message.stepResults && message.stepResults.length > 0 && ( + + + + 📊 执行详情(点击展开查看) + + {message.stepResults.map((result, idx) => ( + + ))} + + )} + + + + ); + + case MessageTypes.ERROR: + return ( + + + } /> + + {message.content} + + + + ); + + default: + return null; + } +}; + +export default AgentChatV3; diff --git a/src/views/AgentChat/index_backup.js b/src/views/AgentChat/index_backup.js new file mode 100644 index 00000000..5bb0ebd5 --- /dev/null +++ b/src/views/AgentChat/index_backup.js @@ -0,0 +1,53 @@ +// src/views/AgentChat/index.js +// Agent聊天页面 + +import React from 'react'; +import { + Box, + Container, + Heading, + Text, + VStack, + useColorModeValue, +} from '@chakra-ui/react'; +import { ChatInterfaceV2 } from '../../components/ChatBot'; + +/** + * Agent聊天页面 + * 提供基于MCP的AI助手对话功能 + */ +const AgentChat = () => { + const bgColor = useColorModeValue('gray.50', 'gray.900'); + const cardBg = useColorModeValue('white', 'gray.800'); + + return ( + + + + {/* 页面标题 */} + + AI投资助手 + + 基于MCP协议的智能投资顾问,支持股票查询、新闻搜索、概念分析等多种功能 + + + + {/* 聊天界面 */} + + + + + + + ); +}; + +export default AgentChat; diff --git a/src/views/AgentChat/index_v3.js b/src/views/AgentChat/index_v3.js new file mode 100644 index 00000000..29d7097c --- /dev/null +++ b/src/views/AgentChat/index_v3.js @@ -0,0 +1,857 @@ +// src/views/AgentChat/index_v3.js +// Agent聊天页面 V3 - 带左侧会话列表和用户信息集成 + +import React, { useState, useEffect, useRef } from 'react'; +import { + Box, + Flex, + VStack, + HStack, + Text, + Input, + IconButton, + Button, + Avatar, + Heading, + Divider, + Spinner, + Badge, + useColorModeValue, + useToast, + Progress, + Fade, + Collapse, + useDisclosure, + InputGroup, + InputLeftElement, + Menu, + MenuButton, + MenuList, + MenuItem, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalBody, + ModalCloseButton, + Tooltip, +} from '@chakra-ui/react'; +import { + FiSend, + FiSearch, + FiPlus, + FiMessageSquare, + FiTrash2, + FiMoreVertical, + FiRefreshCw, + FiDownload, + FiCpu, + FiUser, + FiZap, + FiClock, +} from 'react-icons/fi'; +import { useAuth } from '@contexts/AuthContext'; +import { PlanCard } from '@components/ChatBot/PlanCard'; +import { StepResultCard } from '@components/ChatBot/StepResultCard'; +import { logger } from '@utils/logger'; +import axios from 'axios'; + +/** + * Agent消息类型 + */ +const MessageTypes = { + USER: 'user', + AGENT_THINKING: 'agent_thinking', + AGENT_PLAN: 'agent_plan', + AGENT_EXECUTING: 'agent_executing', + AGENT_RESPONSE: 'agent_response', + ERROR: 'error', +}; + +/** + * Agent聊天页面 V3 + */ +const AgentChatV3 = () => { + const { user } = useAuth(); // 获取当前用户信息 + const toast = useToast(); + + // 会话相关状态 + const [sessions, setSessions] = useState([]); + const [currentSessionId, setCurrentSessionId] = useState(null); + const [isLoadingSessions, setIsLoadingSessions] = useState(true); + + // 消息相关状态 + const [messages, setMessages] = useState([]); + const [inputValue, setInputValue] = useState(''); + const [isProcessing, setIsProcessing] = useState(false); + const [currentProgress, setCurrentProgress] = useState(0); + + // UI 状态 + const [searchQuery, setSearchQuery] = useState(''); + const { isOpen: isSidebarOpen, onToggle: toggleSidebar } = useDisclosure({ defaultIsOpen: true }); + + // Refs + const messagesEndRef = useRef(null); + const inputRef = useRef(null); + + // 颜色主题 + const bgColor = useColorModeValue('gray.50', 'gray.900'); + const sidebarBg = useColorModeValue('white', 'gray.800'); + const chatBg = useColorModeValue('white', 'gray.800'); + const inputBg = useColorModeValue('white', 'gray.700'); + const borderColor = useColorModeValue('gray.200', 'gray.600'); + const hoverBg = useColorModeValue('gray.100', 'gray.700'); + const activeBg = useColorModeValue('blue.50', 'blue.900'); + const userBubbleBg = useColorModeValue('blue.500', 'blue.600'); + const agentBubbleBg = useColorModeValue('white', 'gray.700'); + + // ==================== 会话管理函数 ==================== + + // 加载会话列表 + const loadSessions = async () => { + if (!user?.id) return; + + setIsLoadingSessions(true); + try { + const response = await axios.get('/mcp/agent/sessions', { + params: { user_id: user.id, limit: 50 }, + }); + + if (response.data.success) { + setSessions(response.data.data); + logger.info('会话列表加载成功', response.data.data); + } + } catch (error) { + logger.error('加载会话列表失败', error); + toast({ + title: '加载失败', + description: '无法加载会话列表', + status: 'error', + duration: 3000, + }); + } finally { + setIsLoadingSessions(false); + } + }; + + // 加载会话历史 + const loadSessionHistory = async (sessionId) => { + if (!sessionId) return; + + try { + const response = await axios.get(`/mcp/agent/history/${sessionId}`, { + params: { limit: 100 }, + }); + + if (response.data.success) { + const history = response.data.data; + + // 将历史记录转换为消息格式 + const formattedMessages = history.map((msg, idx) => ({ + id: `${sessionId}-${idx}`, + type: msg.message_type === 'user' ? MessageTypes.USER : MessageTypes.AGENT_RESPONSE, + content: msg.message, + plan: msg.plan ? JSON.parse(msg.plan) : null, + stepResults: msg.steps ? JSON.parse(msg.steps) : null, + timestamp: msg.timestamp, + })); + + setMessages(formattedMessages); + logger.info('会话历史加载成功', formattedMessages); + } + } catch (error) { + logger.error('加载会话历史失败', error); + toast({ + title: '加载失败', + description: '无法加载会话历史', + status: 'error', + duration: 3000, + }); + } + }; + + // 创建新会话 + const createNewSession = () => { + setCurrentSessionId(null); + setMessages([ + { + id: Date.now(), + type: MessageTypes.AGENT_RESPONSE, + content: `你好${user?.nickname || ''}!我是价小前,北京价值前沿科技公司的AI投研助手。\n\n我会通过多步骤分析来帮助你深入了解金融市场。\n\n你可以问我:\n• 全面分析某只股票\n• 某个行业的投资机会\n• 今日市场热点\n• 某个概念板块的表现`, + timestamp: new Date().toISOString(), + }, + ]); + }; + + // 切换会话 + const switchSession = (sessionId) => { + setCurrentSessionId(sessionId); + loadSessionHistory(sessionId); + }; + + // 删除会话(需要后端API支持) + const deleteSession = async (sessionId) => { + // TODO: 实现删除会话的后端API + toast({ + title: '删除会话', + description: '此功能尚未实现', + status: 'info', + duration: 2000, + }); + }; + + // ==================== 消息处理函数 ==================== + + // 自动滚动到底部 + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + // 添加消息 + const addMessage = (message) => { + setMessages((prev) => [...prev, { ...message, id: Date.now() }]); + }; + + // 发送消息 + const handleSendMessage = async () => { + if (!inputValue.trim() || isProcessing) return; + + // 权限检查 + if (user?.id !== 'max') { + toast({ + title: '权限不足', + description: '「价小前投研」功能目前仅对特定用户开放。如需使用,请联系管理员。', + status: 'warning', + duration: 5000, + isClosable: true, + }); + return; + } + + const userMessage = { + type: MessageTypes.USER, + content: inputValue, + timestamp: new Date().toISOString(), + }; + + addMessage(userMessage); + const userInput = inputValue; + setInputValue(''); + setIsProcessing(true); + setCurrentProgress(0); + + let currentPlan = null; + let stepResults = []; + + try { + // 1. 显示思考状态 + addMessage({ + type: MessageTypes.AGENT_THINKING, + content: '正在分析你的问题...', + timestamp: new Date().toISOString(), + }); + + setCurrentProgress(10); + + // 2. 调用后端API(非流式) + const response = await axios.post('/mcp/agent/chat', { + message: userInput, + conversation_history: messages + .filter((m) => m.type === MessageTypes.USER || m.type === MessageTypes.AGENT_RESPONSE) + .map((m) => ({ + isUser: m.type === MessageTypes.USER, + content: m.content, + })), + user_id: user?.id || 'anonymous', + user_nickname: user?.nickname || '匿名用户', + user_avatar: user?.avatar || '', + session_id: currentSessionId, + }); + + // 移除思考消息 + setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_THINKING)); + + if (response.data.success) { + const data = response.data; + + // 更新 session_id(如果是新会话) + if (data.session_id && !currentSessionId) { + setCurrentSessionId(data.session_id); + } + + // 显示执行计划 + if (data.plan) { + currentPlan = data.plan; + addMessage({ + type: MessageTypes.AGENT_PLAN, + content: '已制定执行计划', + plan: data.plan, + timestamp: new Date().toISOString(), + }); + setCurrentProgress(30); + } + + // 显示执行步骤 + if (data.steps && data.steps.length > 0) { + stepResults = data.steps; + addMessage({ + type: MessageTypes.AGENT_EXECUTING, + content: '正在执行步骤...', + plan: currentPlan, + stepResults: stepResults, + timestamp: new Date().toISOString(), + }); + setCurrentProgress(70); + } + + // 移除执行中消息 + setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_EXECUTING)); + + // 显示最终结果 + addMessage({ + type: MessageTypes.AGENT_RESPONSE, + content: data.final_answer || data.message || '处理完成', + plan: currentPlan, + stepResults: stepResults, + metadata: data.metadata, + timestamp: new Date().toISOString(), + }); + + setCurrentProgress(100); + + // 重新加载会话列表 + loadSessions(); + } + } catch (error) { + logger.error('Agent chat error', error); + + // 移除思考/执行中消息 + setMessages((prev) => + prev.filter( + (m) => m.type !== MessageTypes.AGENT_THINKING && m.type !== MessageTypes.AGENT_EXECUTING + ) + ); + + const errorMessage = error.response?.data?.error || error.message || '处理失败'; + + addMessage({ + type: MessageTypes.ERROR, + content: `处理失败:${errorMessage}`, + timestamp: new Date().toISOString(), + }); + + toast({ + title: '处理失败', + description: errorMessage, + status: 'error', + duration: 5000, + isClosable: true, + }); + } finally { + setIsProcessing(false); + setCurrentProgress(0); + inputRef.current?.focus(); + } + }; + + // 处理键盘事件 + const handleKeyPress = (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSendMessage(); + } + }; + + // 清空对话 + const handleClearChat = () => { + createNewSession(); + }; + + // 导出对话 + const handleExportChat = () => { + const chatText = messages + .filter((m) => m.type === MessageTypes.USER || m.type === MessageTypes.AGENT_RESPONSE) + .map((msg) => `[${msg.type === MessageTypes.USER ? '用户' : '价小前'}] ${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); + }; + + // ==================== 初始化 ==================== + + useEffect(() => { + if (user) { + loadSessions(); + createNewSession(); + } + }, [user]); + + // ==================== 渲染 ==================== + + // 快捷问题 + const quickQuestions = [ + '全面分析贵州茅台这只股票', + '今日涨停股票有哪些亮点', + '新能源概念板块的投资机会', + '半导体行业最新动态', + ]; + + // 筛选会话 + const filteredSessions = sessions.filter( + (session) => + !searchQuery || + session.last_message?.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + return ( + + {/* 左侧会话列表 */} + + + {/* 侧边栏头部 */} + + + + {/* 搜索框 */} + + + + + setSearchQuery(e.target.value)} + /> + + + + {/* 会话列表 */} + + {isLoadingSessions ? ( + + + + ) : filteredSessions.length === 0 ? ( + + + + {searchQuery ? '没有找到匹配的对话' : '暂无对话记录'} + + + ) : ( + filteredSessions.map((session) => ( + switchSession(session.session_id)} + > + + + + {session.last_message || '新对话'} + + + + + {new Date(session.last_timestamp).toLocaleDateString('zh-CN', { + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + })} + + + {session.message_count} 条 + + + + + + } + size="xs" + variant="ghost" + onClick={(e) => e.stopPropagation()} + /> + + } + color="red.500" + onClick={(e) => { + e.stopPropagation(); + deleteSession(session.session_id); + }} + > + 删除对话 + + + + + + )) + )} + + + {/* 用户信息 */} + + + + + + {user?.nickname || '未登录'} + + + {user?.id || 'anonymous'} + + + + + + + + {/* 主聊天区域 */} + + {/* 聊天头部 */} + + + + } + size="sm" + variant="ghost" + aria-label="切换侧边栏" + onClick={toggleSidebar} + /> + } /> + + 价小前投研 + + + + + 智能分析 + + + + 多步骤深度研究 + + + + + + + } + size="sm" + variant="ghost" + aria-label="清空对话" + onClick={handleClearChat} + /> + } + size="sm" + variant="ghost" + aria-label="导出对话" + onClick={handleExportChat} + /> + + + + {/* 进度条 */} + {isProcessing && ( + + )} + + + {/* 消息列表 */} + + + {messages.map((message) => ( + + + + ))} +
+ + + + {/* 快捷问题 */} + {messages.length <= 2 && !isProcessing && ( + + + 💡 试试这些问题: + + + {quickQuestions.map((question, idx) => ( + + ))} + + + )} + + {/* 输入框 */} + + + setInputValue(e.target.value)} + onKeyPress={handleKeyPress} + placeholder="输入你的问题,我会进行深度分析..." + bg={inputBg} + border="1px" + borderColor={borderColor} + _focus={{ borderColor: 'blue.500', boxShadow: '0 0 0 1px #3182CE' }} + mr={2} + disabled={isProcessing} + size="lg" + /> + : } + colorScheme="blue" + aria-label="发送" + onClick={handleSendMessage} + isLoading={isProcessing} + disabled={!inputValue.trim() || isProcessing} + size="lg" + /> + + + + + ); +}; + +/** + * 消息渲染器 + */ +const MessageRenderer = ({ message, userAvatar }) => { + const userBubbleBg = useColorModeValue('blue.500', 'blue.600'); + const agentBubbleBg = useColorModeValue('white', 'gray.700'); + const borderColor = useColorModeValue('gray.200', 'gray.600'); + + switch (message.type) { + case MessageTypes.USER: + return ( + + + + + {message.content} + + + } /> + + + ); + + case MessageTypes.AGENT_THINKING: + return ( + + + } /> + + + + + {message.content} + + + + + + ); + + case MessageTypes.AGENT_PLAN: + return ( + + + } /> + + + + + + ); + + case MessageTypes.AGENT_EXECUTING: + return ( + + + } /> + + + {message.stepResults?.map((result, idx) => ( + + ))} + + + + ); + + case MessageTypes.AGENT_RESPONSE: + return ( + + + } /> + + {/* 最终总结 */} + + + {message.content} + + + {/* 元数据 */} + {message.metadata && ( + + 总步骤: {message.metadata.total_steps} + ✓ {message.metadata.successful_steps} + {message.metadata.failed_steps > 0 && ( + ✗ {message.metadata.failed_steps} + )} + 耗时: {message.metadata.total_execution_time?.toFixed(1)}s + + )} + + + {/* 执行详情(可选) */} + {message.plan && message.stepResults && message.stepResults.length > 0 && ( + + + + 📊 执行详情(点击展开查看) + + {message.stepResults.map((result, idx) => ( + + ))} + + )} + + + + ); + + case MessageTypes.ERROR: + return ( + + + } /> + + {message.content} + + + + ); + + default: + return null; + } +}; + +export default AgentChatV3;