diff --git a/src/views/AgentChat/index.js b/src/views/AgentChat/index.js index 7446357f..7db61de8 100644 --- a/src/views/AgentChat/index.js +++ b/src/views/AgentChat/index.js @@ -3,101 +3,23 @@ // 使用 Framer Motion 物理动画引擎 + 毛玻璃效果 import React, { useState, useEffect, useRef } from 'react'; -import { motion, AnimatePresence } from 'framer-motion'; -import { - Box, - Button, - Input, - Avatar, - Badge, - Divider, - Spinner, - Tooltip, - Checkbox, - CheckboxGroup, - Kbd, - Accordion, - AccordionItem, - AccordionButton, - AccordionPanel, - AccordionIcon, - Tabs, - TabList, - TabPanels, - Tab, - TabPanel, - useToast, - VStack, - HStack, - Text, - Flex, - IconButton, - useColorMode, - Card, - CardBody, - Tag, - TagLabel, - TagCloseButton, -} from '@chakra-ui/react'; +import { Box, Flex, useToast, useColorMode } from '@chakra-ui/react'; import { useAuth } from '@contexts/AuthContext'; import { logger } from '@utils/logger'; import axios from 'axios'; -// 图标 - 使用 Lucide Icons -import { - Send, - Plus, - Search, - MessageSquare, - Trash2, - MoreVertical, - RefreshCw, - Download, - Cpu, - User, - Zap, - Clock, - Settings, - ChevronLeft, - ChevronRight, - Activity, - Code, - Database, - TrendingUp, - FileText, - BookOpen, - Menu, - X, - Check, - Circle, - Maximize2, - Minimize2, - Copy, - ThumbsUp, - ThumbsDown, - Sparkles, - Brain, - Rocket, - Paperclip, - Image as ImageIcon, - File, - Calendar, - Globe, - DollarSign, - Newspaper, - BarChart3, - PieChart, - LineChart, - Briefcase, - Users, -} from 'lucide-react'; +// 注意:图标导入已移至子组件中,主组件不再需要导入 // 常量配置 - 从 TypeScript 模块导入 -import { animations } from './constants/animations'; import { MessageTypes } from './constants/messageTypes'; -import { AVAILABLE_MODELS, DEFAULT_MODEL_ID } from './constants/models'; -import { MCP_TOOLS, TOOL_CATEGORIES, DEFAULT_SELECTED_TOOLS } from './constants/tools'; -import { quickQuestions } from './constants/quickQuestions'; +import { DEFAULT_MODEL_ID } from './constants/models'; +import { DEFAULT_SELECTED_TOOLS } from './constants/tools'; + +// 拆分后的子组件 +import BackgroundEffects from './components/BackgroundEffects'; +import LeftSidebar from './components/LeftSidebar'; +import ChatArea from './components/ChatArea'; +import RightSidebar from './components/RightSidebar'; /** * Agent Chat - 主组件(HeroUI v3 深色主题) @@ -125,7 +47,6 @@ const AgentChat = () => { const [isProcessing, setIsProcessing] = useState(false); // UI 状态 - const [searchQuery, setSearchQuery] = useState(''); const [selectedModel, setSelectedModel] = useState(DEFAULT_MODEL_ID); const [selectedTools, setSelectedTools] = useState(DEFAULT_SELECTED_TOOLS); const [isLeftSidebarOpen, setIsLeftSidebarOpen] = useState(true); @@ -360,1464 +281,61 @@ const AgentChat = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); - // ==================== 按日期分组会话 ==================== - const groupSessionsByDate = (sessions) => { - const today = new Date(); - const yesterday = new Date(today); - yesterday.setDate(yesterday.getDate() - 1); - const weekAgo = new Date(today); - weekAgo.setDate(weekAgo.getDate() - 7); - - const groups = { - today: [], - yesterday: [], - thisWeek: [], - older: [], - }; - - sessions.forEach(session => { - const sessionDate = new Date(session.created_at || session.timestamp); - const daysDiff = Math.floor((today - sessionDate) / (1000 * 60 * 60 * 24)); - - if (daysDiff === 0) { - groups.today.push(session); - } else if (daysDiff === 1) { - groups.yesterday.push(session); - } else if (daysDiff <= 7) { - groups.thisWeek.push(session); - } else { - groups.older.push(session); - } - }); - - return groups; - }; - - const sessionGroups = groupSessionsByDate(sessions); - const filteredSessions = searchQuery - ? sessions.filter((s) => - s.title?.toLowerCase().includes(searchQuery.toLowerCase()) || - s.session_id?.toLowerCase().includes(searchQuery.toLowerCase()) - ) - : sessions; - - // quickQuestions 已移动到 constants/quickQuestions.ts - return ( - {/* 背景渐变层 - 移到 Flex 内部 */} - + + {/* 左侧栏 */} + setIsLeftSidebarOpen(false)} + sessions={sessions} + currentSessionId={currentSessionId} + onSessionSwitch={switchSession} + onNewSession={createNewSession} + isLoadingSessions={isLoadingSessions} + user={user} /> - {/* 背景装饰光效 */} - - setIsLeftSidebarOpen(true)} + onToggleRightSidebar={() => setIsRightSidebarOpen(true)} + onNewSession={createNewSession} + userAvatar={user?.avatar} + messagesEndRef={messagesEndRef} + inputRef={inputRef} + fileInputRef={fileInputRef} /> - {/* 左侧栏 - 深色毛玻璃 */} - - {isLeftSidebarOpen && ( - - - - - - - - 对话历史 - - - - - - } - onClick={createNewSession} - bg="rgba(255, 255, 255, 0.05)" - color="gray.300" - backdropFilter="blur(10px)" - border="1px solid" - borderColor="rgba(255, 255, 255, 0.1)" - _hover={{ - bg: "rgba(59, 130, 246, 0.2)", - borderColor: "blue.400", - color: "blue.300", - boxShadow: "0 0 12px rgba(59, 130, 246, 0.3)" - }} - /> - - - - - } - onClick={() => setIsLeftSidebarOpen(false)} - bg="rgba(255, 255, 255, 0.05)" - color="gray.300" - backdropFilter="blur(10px)" - border="1px solid" - borderColor="rgba(255, 255, 255, 0.1)" - _hover={{ - bg: "rgba(255, 255, 255, 0.1)", - borderColor: "purple.400", - color: "white" - }} - /> - - - - - - - - - - setSearchQuery(e.target.value)} - size="sm" - variant="outline" - pl={10} - bg="rgba(255, 255, 255, 0.05)" - backdropFilter="blur(10px)" - 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: "purple.400", - boxShadow: "0 0 0 1px var(--chakra-colors-purple-400), 0 0 12px rgba(139, 92, 246, 0.3)", - bg: "rgba(255, 255, 255, 0.08)" - }} - /> - - - - - {/* 按日期分组显示会话 */} - {sessionGroups.today.length > 0 && ( - - 今天 - - {sessionGroups.today.map((session, idx) => ( - - switchSession(session.session_id)} - /> - - ))} - - - )} - - {sessionGroups.yesterday.length > 0 && ( - - 昨天 - - {sessionGroups.yesterday.map((session) => ( - switchSession(session.session_id)} - /> - ))} - - - )} - - {sessionGroups.thisWeek.length > 0 && ( - - 本周 - - {sessionGroups.thisWeek.map((session) => ( - switchSession(session.session_id)} - /> - ))} - - - )} - - {sessionGroups.older.length > 0 && ( - - 更早 - - {sessionGroups.older.map((session) => ( - switchSession(session.session_id)} - /> - ))} - - - )} - - {isLoadingSessions && ( - - - - )} - - {sessions.length === 0 && !isLoadingSessions && ( - - - 还没有对话历史 - 开始一个新对话吧! - - )} - - - - - - - - {user?.nickname || '未登录'} - - - {user?.subscription_type || 'free'} - - - - - - - )} - - - {/* 中间主聊天区域 */} - - {/* 顶部标题栏 - 深色毛玻璃 */} - - - - {!isLeftSidebarOpen && ( - - } - onClick={() => setIsLeftSidebarOpen(true)} - bg="rgba(255, 255, 255, 0.05)" - color="gray.400" - backdropFilter="blur(10px)" - border="1px solid" - borderColor="rgba(255, 255, 255, 0.1)" - _hover={{ - bg: "rgba(255, 255, 255, 0.1)", - color: "white" - }} - /> - - )} - - - } - bgGradient="linear(to-br, purple.500, pink.500)" - boxShadow="0 0 20px rgba(236, 72, 153, 0.5)" - /> - - - - - 价小前投研 AI - - - - - 智能分析 - - - {AVAILABLE_MODELS.find((m) => m.id === selectedModel)?.name} - - - - - - - - - } - onClick={createNewSession} - bg="rgba(255, 255, 255, 0.05)" - color="gray.400" - backdropFilter="blur(10px)" - border="1px solid" - borderColor="rgba(255, 255, 255, 0.1)" - _hover={{ - bg: "rgba(255, 255, 255, 0.1)", - color: "white", - borderColor: "purple.400", - boxShadow: "0 0 12px rgba(139, 92, 246, 0.3)" - }} - /> - - - {!isRightSidebarOpen && ( - - } - onClick={() => setIsRightSidebarOpen(true)} - bg="rgba(255, 255, 255, 0.05)" - color="gray.400" - backdropFilter="blur(10px)" - border="1px solid" - borderColor="rgba(255, 255, 255, 0.1)" - _hover={{ - bg: "rgba(255, 255, 255, 0.1)", - color: "white" - }} - /> - - )} - - - - - {/* 消息列表 */} - - - - - {messages.map((message) => ( - - - - ))} - -
- - - - - {/* 快捷问题 */} - - {messages.length <= 2 && !isProcessing && ( - - - - - - 快速开始 - - - {quickQuestions.map((question, idx) => ( - - - - ))} - - - - - )} - - - {/* 输入栏 - 深色毛玻璃 */} - - - {/* 已上传文件预览 */} - {uploadedFiles.length > 0 && ( - - {uploadedFiles.map((file, idx) => ( - - - {file.name} - removeFile(idx)} color="gray.400" /> - - - ))} - - )} - - - - - - - } - onClick={() => fileInputRef.current?.click()} - bg="rgba(255, 255, 255, 0.05)" - color="gray.300" - backdropFilter="blur(10px)" - border="1px solid" - borderColor="rgba(255, 255, 255, 0.1)" - _hover={{ - bg: "rgba(255, 255, 255, 0.1)", - borderColor: "purple.400", - color: "white", - boxShadow: "0 0 12px rgba(139, 92, 246, 0.3)" - }} - /> - - - - - - } - onClick={() => { - fileInputRef.current?.setAttribute('accept', 'image/*'); - fileInputRef.current?.click(); - }} - bg="rgba(255, 255, 255, 0.05)" - color="gray.300" - backdropFilter="blur(10px)" - border="1px solid" - borderColor="rgba(255, 255, 255, 0.1)" - _hover={{ - bg: "rgba(255, 255, 255, 0.1)", - borderColor: "purple.400", - color: "white", - boxShadow: "0 0 12px rgba(139, 92, 246, 0.3)" - }} - /> - - - - setInputValue(e.target.value)} - onKeyDown={handleKeyPress} - placeholder="输入你的问题... (Enter 发送, Shift+Enter 换行)" - isDisabled={isProcessing} - size="lg" - variant="outline" - borderWidth={2} - bg="rgba(255, 255, 255, 0.05)" - backdropFilter="blur(10px)" - 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: "purple.400", - boxShadow: "0 0 0 1px var(--chakra-colors-purple-400), 0 0 12px rgba(139, 92, 246, 0.3)", - bg: "rgba(255, 255, 255, 0.08)" - }} - /> - - - } - onClick={handleSendMessage} - isLoading={isProcessing} - isDisabled={!inputValue.trim() || isProcessing} - bgGradient="linear(to-r, blue.500, purple.600)" - color="white" - _hover={{ - bgGradient: "linear(to-r, blue.600, purple.700)", - boxShadow: "0 8px 20px rgba(139, 92, 246, 0.4)" - }} - _active={{ - transform: "translateY(0)", - boxShadow: "0 4px 12px rgba(139, 92, 246, 0.3)" - }} - /> - - - - - - Enter - 发送 - - - Shift - + - Enter - 换行 - - - - - - - {/* 右侧栏 - 深色配置中心 */} - - {isRightSidebarOpen && ( - - - - - - - - 配置中心 - - - - - } - onClick={() => setIsRightSidebarOpen(false)} - bg="rgba(255, 255, 255, 0.05)" - color="gray.300" - backdropFilter="blur(10px)" - border="1px solid" - borderColor="rgba(255, 255, 255, 0.1)" - _hover={{ - bg: "rgba(255, 255, 255, 0.1)", - borderColor: "purple.400", - color: "white" - }} - /> - - - - - - - - - - - - 模型 - - - - - - 工具 - {selectedTools.length > 0 && ( - - {selectedTools.length} - - )} - - - - - - 统计 - - - - - - {/* 模型选择 */} - - - {AVAILABLE_MODELS.map((model, idx) => ( - - setSelectedModel(model.id)} - bg={selectedModel === model.id - ? 'rgba(139, 92, 246, 0.15)' - : 'rgba(255, 255, 255, 0.05)'} - backdropFilter="blur(12px)" - borderWidth={2} - borderColor={selectedModel === model.id - ? 'purple.400' - : 'rgba(255, 255, 255, 0.1)'} - _hover={{ - borderColor: selectedModel === model.id - ? 'purple.400' - : 'rgba(255, 255, 255, 0.2)', - boxShadow: selectedModel === model.id - ? "0 8px 20px rgba(139, 92, 246, 0.4)" - : "0 4px 12px rgba(0, 0, 0, 0.3)" - }} - transition="all 0.3s" - > - - - - {model.icon} - - - - {model.name} - - - {model.description} - - - {selectedModel === model.id && ( - - - - )} - - - - - ))} - - - - {/* 工具选择 */} - - - {Object.entries(TOOL_CATEGORIES).map(([category, tools], catIdx) => ( - - - - - {category} - - {tools.filter(t => selectedTools.includes(t.id)).length}/{tools.length} - - - - - - - - {tools.map((tool) => ( - - - - {tool.icon} - - {tool.name} - {tool.description} - - - - - ))} - - - - - - ))} - - - - - - - - - - - - - {/* 统计信息 */} - - - - - - - - 对话数 - - {sessions.length} - - - - - - - - - - - - - - 消息数 - - {messages.length} - - - - - - - - - - - - - - 已选工具 - - {selectedTools.length} - - - - - - - - - - - - - - - )} - + {/* 右侧栏 */} + setIsRightSidebarOpen(false)} + selectedModel={selectedModel} + onModelChange={setSelectedModel} + selectedTools={selectedTools} + onToolsChange={setSelectedTools} + sessionsCount={sessions.length} + messagesCount={messages.length} + /> ); }; export default AgentChat; - -/** - * 会话卡片组件 - */ -const SessionCard = ({ session, isActive, onPress }) => { - return ( - - - - - - - {session.title || '新对话'} - - - {new Date(session.created_at || session.timestamp).toLocaleString('zh-CN', { - month: 'numeric', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - })} - - - {session.message_count && ( - - {session.message_count} - - )} - - - - - ); -}; - -/** - * 消息渲染器 - */ -const MessageRenderer = ({ message, userAvatar }) => { - switch (message.type) { - case MessageTypes.USER: - return ( - - - - - - - {message.content} - - {message.files && message.files.length > 0 && ( - - {message.files.map((file, idx) => ( - - - {file.name} - - ))} - - )} - - - - } - size="sm" - bgGradient="linear(to-br, blue.500, purple.600)" - boxShadow="0 0 12px rgba(139, 92, 246, 0.4)" - /> - - - ); - - case MessageTypes.AGENT_THINKING: - return ( - - - } - size="sm" - bgGradient="linear(to-br, purple.500, pink.500)" - boxShadow="0 0 12px rgba(236, 72, 153, 0.4)" - /> - - - - - - {message.content} - - - - - - - ); - - case MessageTypes.AGENT_RESPONSE: - return ( - - - } - size="sm" - bgGradient="linear(to-br, purple.500, pink.500)" - boxShadow="0 0 12px rgba(236, 72, 153, 0.4)" - /> - - - - - {message.content} - - - {message.stepResults && message.stepResults.length > 0 && ( - - - - )} - - - - - } - onClick={() => navigator.clipboard.writeText(message.content)} - bg="rgba(255, 255, 255, 0.05)" - color="gray.400" - _hover={{ - color: "white", - bg: "rgba(255, 255, 255, 0.1)" - }} - /> - - - - - } - bg="rgba(255, 255, 255, 0.05)" - color="gray.400" - _hover={{ - color: "green.400", - bg: "rgba(16, 185, 129, 0.1)", - boxShadow: "0 0 12px rgba(16, 185, 129, 0.3)" - }} - /> - - - - - } - bg="rgba(255, 255, 255, 0.05)" - color="gray.400" - _hover={{ - color: "red.400", - bg: "rgba(239, 68, 68, 0.1)", - boxShadow: "0 0 12px rgba(239, 68, 68, 0.3)" - }} - /> - - - - {new Date(message.timestamp).toLocaleTimeString('zh-CN', { - hour: '2-digit', - minute: '2-digit', - })} - - - - - - - - ); - - case MessageTypes.ERROR: - return ( - - - - - {message.content} - - - - - ); - - default: - return null; - } -}; - -/** - * 执行步骤显示组件 - */ -const ExecutionStepsDisplay = ({ steps, plan }) => { - return ( - - - - - - 执行详情 - - {steps.length} 步骤 - - - - - - - {steps.map((result, idx) => ( - - - - - - 步骤 {idx + 1}: {result.tool_name} - - - {result.status} - - - - {result.execution_time?.toFixed(2)}s - - {result.error && ( - ⚠️ {result.error} - )} - - - - ))} - - - - - ); -}; diff --git a/src/views/AgentChat/index.js.bak b/src/views/AgentChat/index.js.bak new file mode 100644 index 00000000..60a2ecf8 --- /dev/null +++ b/src/views/AgentChat/index.js.bak @@ -0,0 +1,1529 @@ +// src/views/AgentChat/index.js +// 超炫酷的 AI 投研助手 - HeroUI v3 现代深色主题版本 +// 使用 Framer Motion 物理动画引擎 + 毛玻璃效果 + +import React, { useState, useEffect, useRef } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { + Box, + Button, + Input, + Avatar, + Badge, + Divider, + Spinner, + Tooltip, + Checkbox, + CheckboxGroup, + Kbd, + Accordion, + AccordionItem, + AccordionButton, + AccordionPanel, + AccordionIcon, + Tabs, + TabList, + TabPanels, + Tab, + TabPanel, + useToast, + VStack, + HStack, + Text, + Flex, + IconButton, + useColorMode, + Card, + CardBody, + Tag, + TagLabel, + TagCloseButton, +} from '@chakra-ui/react'; +import { useAuth } from '@contexts/AuthContext'; +import { logger } from '@utils/logger'; +import axios from 'axios'; + +// 图标 - 使用 Lucide Icons +import { + Send, + Plus, + Search, + MessageSquare, + Trash2, + MoreVertical, + RefreshCw, + Download, + Cpu, + User, + Zap, + Clock, + Settings, + ChevronLeft, + ChevronRight, + Activity, + Code, + Database, + TrendingUp, + FileText, + BookOpen, + Menu, + X, + Check, + Circle, + Maximize2, + Minimize2, + Copy, + ThumbsUp, + ThumbsDown, + Sparkles, + Brain, + Rocket, + Paperclip, + Image as ImageIcon, + File, + Calendar, + Globe, + DollarSign, + Newspaper, + BarChart3, + PieChart, + LineChart, + Briefcase, + Users, +} from 'lucide-react'; + +// 常量配置 - 从 TypeScript 模块导入 +import { MessageTypes } from './constants/messageTypes'; +import { DEFAULT_MODEL_ID } from './constants/models'; +import { DEFAULT_SELECTED_TOOLS } from './constants/tools'; + +// 拆分后的子组件 +import BackgroundEffects from './components/BackgroundEffects'; +import LeftSidebar from './components/LeftSidebar'; +import ChatArea from './components/ChatArea'; +import RightSidebar from './components/RightSidebar'; + +/** + * Agent Chat - 主组件(HeroUI v3 深色主题) + * + * 注意:所有常量配置已提取到 constants/ 目录: + * - animations: constants/animations.ts + * - MessageTypes: constants/messageTypes.ts + * - AVAILABLE_MODELS: constants/models.ts + * - MCP_TOOLS, TOOL_CATEGORIES: constants/tools.ts + * - quickQuestions: constants/quickQuestions.ts + */ +const AgentChat = () => { + const { user } = useAuth(); + const toast = useToast(); + const { setColorMode } = useColorMode(); + + // 会话管理 + const [sessions, setSessions] = useState([]); + const [currentSessionId, setCurrentSessionId] = useState(null); + const [isLoadingSessions, setIsLoadingSessions] = useState(false); + + // 消息管理 + const [messages, setMessages] = useState([]); + const [inputValue, setInputValue] = useState(''); + const [isProcessing, setIsProcessing] = useState(false); + + // UI 状态 + const [searchQuery, setSearchQuery] = useState(''); + const [selectedModel, setSelectedModel] = useState(DEFAULT_MODEL_ID); + const [selectedTools, setSelectedTools] = useState(DEFAULT_SELECTED_TOOLS); + const [isLeftSidebarOpen, setIsLeftSidebarOpen] = useState(true); + const [isRightSidebarOpen, setIsRightSidebarOpen] = useState(true); + + // 文件上传 + const [uploadedFiles, setUploadedFiles] = useState([]); + const fileInputRef = useRef(null); + + // Refs + const messagesEndRef = useRef(null); + const inputRef = useRef(null); + + // ==================== 启用深色模式 ==================== + useEffect(() => { + // 为 AgentChat 页面强制启用深色模式 + setColorMode('dark'); + document.documentElement.classList.add('dark'); + + return () => { + // 组件卸载时不移除,让其他页面自己控制 + // document.documentElement.classList.remove('dark'); + }; + }, [setColorMode]); + + // ==================== API 调用函数 ==================== + + 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); + } + } catch (error) { + logger.error('加载会话列表失败', error); + } 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); + } + } catch (error) { + logger.error('加载会话历史失败', error); + } + }; + + const createNewSession = () => { + setCurrentSessionId(null); + setMessages([ + { + id: Date.now(), + type: MessageTypes.AGENT_RESPONSE, + content: `你好${user?.nickname || ''}!👋\n\n我是**价小前**,你的 AI 投研助手。\n\n**我能做什么?**\n• 📊 全面分析股票基本面和技术面\n• 🔥 追踪市场热点和涨停板块\n• 📈 研究行业趋势和投资机会\n• 📰 汇总最新财经新闻和研报\n\n直接输入你的问题开始探索!`, + timestamp: new Date().toISOString(), + }, + ]); + }; + + const switchSession = (sessionId) => { + setCurrentSessionId(sessionId); + loadSessionHistory(sessionId); + }; + + const handleSendMessage = async () => { + if (!inputValue.trim() || isProcessing) return; + + const userMessage = { + type: MessageTypes.USER, + content: inputValue, + timestamp: new Date().toISOString(), + files: uploadedFiles.length > 0 ? uploadedFiles : undefined, + }; + + addMessage(userMessage); + const userInput = inputValue; + setInputValue(''); + setUploadedFiles([]); + setIsProcessing(true); + + try { + addMessage({ + type: MessageTypes.AGENT_THINKING, + content: '正在分析你的问题...', + timestamp: new Date().toISOString(), + }); + + 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 || '', + subscription_type: user?.subscription_type || 'free', + session_id: currentSessionId, + model: selectedModel, + tools: selectedTools, + files: uploadedFiles.length > 0 ? uploadedFiles : undefined, + }); + + setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_THINKING)); + + if (response.data.success) { + const data = response.data; + if (data.session_id && !currentSessionId) { + setCurrentSessionId(data.session_id); + } + + if (data.plan) { + addMessage({ + type: MessageTypes.AGENT_PLAN, + content: '已制定执行计划', + plan: data.plan, + timestamp: new Date().toISOString(), + }); + } + + if (data.steps && data.steps.length > 0) { + addMessage({ + type: MessageTypes.AGENT_EXECUTING, + content: '正在执行步骤...', + plan: data.plan, + stepResults: data.steps, + timestamp: new Date().toISOString(), + }); + } + + setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_EXECUTING)); + + addMessage({ + type: MessageTypes.AGENT_RESPONSE, + content: data.final_answer || data.message || '处理完成', + plan: data.plan, + stepResults: data.steps, + metadata: data.metadata, + timestamp: new Date().toISOString(), + }); + + 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, + }); + } finally { + setIsProcessing(false); + } + }; + + // 文件上传处理 + const handleFileSelect = (event) => { + const files = Array.from(event.target.files || []); + const fileData = files.map(file => ({ + name: file.name, + size: file.size, + type: file.type, + // 实际上传时需要转换为 base64 或上传到服务器 + url: URL.createObjectURL(file), + })); + setUploadedFiles(prev => [...prev, ...fileData]); + }; + + const removeFile = (index) => { + setUploadedFiles(prev => prev.filter((_, i) => i !== index)); + }; + + const addMessage = (message) => { + setMessages((prev) => [ + ...prev, + { + id: Date.now() + Math.random(), + ...message, + }, + ]); + }; + + const handleKeyPress = (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSendMessage(); + } + }; + + useEffect(() => { + loadSessions(); + createNewSession(); + }, [user]); + + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages]); + + return ( + + + {/* 背景渐变装饰 */} + + + {/* 左侧栏 */} + setIsLeftSidebarOpen(false)} + sessions={sessions} + currentSessionId={currentSessionId} + onSessionSwitch={switchSession} + onNewSession={createNewSession} + isLoadingSessions={isLoadingSessions} + user={user} + /> + + {/* 中间主聊天区域 */} + + {/* 顶部标题栏 - 深色毛玻璃 */} + + + + {!isLeftSidebarOpen && ( + + } + onClick={() => setIsLeftSidebarOpen(true)} + bg="rgba(255, 255, 255, 0.05)" + color="gray.400" + backdropFilter="blur(10px)" + border="1px solid" + borderColor="rgba(255, 255, 255, 0.1)" + _hover={{ + bg: "rgba(255, 255, 255, 0.1)", + color: "white" + }} + /> + + )} + + + } + bgGradient="linear(to-br, purple.500, pink.500)" + boxShadow="0 0 20px rgba(236, 72, 153, 0.5)" + /> + + + + + 价小前投研 AI + + + + + 智能分析 + + + {AVAILABLE_MODELS.find((m) => m.id === selectedModel)?.name} + + + + + + + + + } + onClick={createNewSession} + bg="rgba(255, 255, 255, 0.05)" + color="gray.400" + backdropFilter="blur(10px)" + border="1px solid" + borderColor="rgba(255, 255, 255, 0.1)" + _hover={{ + bg: "rgba(255, 255, 255, 0.1)", + color: "white", + borderColor: "purple.400", + boxShadow: "0 0 12px rgba(139, 92, 246, 0.3)" + }} + /> + + + {!isRightSidebarOpen && ( + + } + onClick={() => setIsRightSidebarOpen(true)} + bg="rgba(255, 255, 255, 0.05)" + color="gray.400" + backdropFilter="blur(10px)" + border="1px solid" + borderColor="rgba(255, 255, 255, 0.1)" + _hover={{ + bg: "rgba(255, 255, 255, 0.1)", + color: "white" + }} + /> + + )} + + + + + {/* 消息列表 */} + + + + + {messages.map((message) => ( + + + + ))} + +
+ + + + + {/* 快捷问题 */} + + {messages.length <= 2 && !isProcessing && ( + + + + + + 快速开始 + + + {quickQuestions.map((question, idx) => ( + + + + ))} + + + + + )} + + + {/* 输入栏 - 深色毛玻璃 */} + + + {/* 已上传文件预览 */} + {uploadedFiles.length > 0 && ( + + {uploadedFiles.map((file, idx) => ( + + + {file.name} + removeFile(idx)} color="gray.400" /> + + + ))} + + )} + + + + + + + } + onClick={() => fileInputRef.current?.click()} + bg="rgba(255, 255, 255, 0.05)" + color="gray.300" + backdropFilter="blur(10px)" + border="1px solid" + borderColor="rgba(255, 255, 255, 0.1)" + _hover={{ + bg: "rgba(255, 255, 255, 0.1)", + borderColor: "purple.400", + color: "white", + boxShadow: "0 0 12px rgba(139, 92, 246, 0.3)" + }} + /> + + + + + + } + onClick={() => { + fileInputRef.current?.setAttribute('accept', 'image/*'); + fileInputRef.current?.click(); + }} + bg="rgba(255, 255, 255, 0.05)" + color="gray.300" + backdropFilter="blur(10px)" + border="1px solid" + borderColor="rgba(255, 255, 255, 0.1)" + _hover={{ + bg: "rgba(255, 255, 255, 0.1)", + borderColor: "purple.400", + color: "white", + boxShadow: "0 0 12px rgba(139, 92, 246, 0.3)" + }} + /> + + + + setInputValue(e.target.value)} + onKeyDown={handleKeyPress} + placeholder="输入你的问题... (Enter 发送, Shift+Enter 换行)" + isDisabled={isProcessing} + size="lg" + variant="outline" + borderWidth={2} + bg="rgba(255, 255, 255, 0.05)" + backdropFilter="blur(10px)" + 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: "purple.400", + boxShadow: "0 0 0 1px var(--chakra-colors-purple-400), 0 0 12px rgba(139, 92, 246, 0.3)", + bg: "rgba(255, 255, 255, 0.08)" + }} + /> + + + } + onClick={handleSendMessage} + isLoading={isProcessing} + isDisabled={!inputValue.trim() || isProcessing} + bgGradient="linear(to-r, blue.500, purple.600)" + color="white" + _hover={{ + bgGradient: "linear(to-r, blue.600, purple.700)", + boxShadow: "0 8px 20px rgba(139, 92, 246, 0.4)" + }} + _active={{ + transform: "translateY(0)", + boxShadow: "0 4px 12px rgba(139, 92, 246, 0.3)" + }} + /> + + + + + + Enter + 发送 + + + Shift + + + Enter + 换行 + + + + + + + {/* 右侧栏 - 深色配置中心 */} + + {isRightSidebarOpen && ( + + + + + + + + 配置中心 + + + + + } + onClick={() => setIsRightSidebarOpen(false)} + bg="rgba(255, 255, 255, 0.05)" + color="gray.300" + backdropFilter="blur(10px)" + border="1px solid" + borderColor="rgba(255, 255, 255, 0.1)" + _hover={{ + bg: "rgba(255, 255, 255, 0.1)", + borderColor: "purple.400", + color: "white" + }} + /> + + + + + + + + + + + + 模型 + + + + + + 工具 + {selectedTools.length > 0 && ( + + {selectedTools.length} + + )} + + + + + + 统计 + + + + + + {/* 模型选择 */} + + + {AVAILABLE_MODELS.map((model, idx) => ( + + setSelectedModel(model.id)} + bg={selectedModel === model.id + ? 'rgba(139, 92, 246, 0.15)' + : 'rgba(255, 255, 255, 0.05)'} + backdropFilter="blur(12px)" + borderWidth={2} + borderColor={selectedModel === model.id + ? 'purple.400' + : 'rgba(255, 255, 255, 0.1)'} + _hover={{ + borderColor: selectedModel === model.id + ? 'purple.400' + : 'rgba(255, 255, 255, 0.2)', + boxShadow: selectedModel === model.id + ? "0 8px 20px rgba(139, 92, 246, 0.4)" + : "0 4px 12px rgba(0, 0, 0, 0.3)" + }} + transition="all 0.3s" + > + + + + {model.icon} + + + + {model.name} + + + {model.description} + + + {selectedModel === model.id && ( + + + + )} + + + + + ))} + + + + {/* 工具选择 */} + + + {Object.entries(TOOL_CATEGORIES).map(([category, tools], catIdx) => ( + + + + + {category} + + {tools.filter(t => selectedTools.includes(t.id)).length}/{tools.length} + + + + + + + + {tools.map((tool) => ( + + + + {tool.icon} + + {tool.name} + {tool.description} + + + + + ))} + + + + + + ))} + + + + + + + + + + + + + {/* 统计信息 */} + + + + + + + + 对话数 + + {sessions.length} + + + + + + + + + + + + + + 消息数 + + {messages.length} + + + + + + + + + + + + + + 已选工具 + + {selectedTools.length} + + + + + + + + + + + + + + + )} + + + + ); +}; + +export default AgentChat; + return ( + + + + + + + {session.title || '新对话'} + + + {new Date(session.created_at || session.timestamp).toLocaleString('zh-CN', { + month: 'numeric', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + })} + + + {session.message_count && ( + + {session.message_count} + + )} + + + + + ); +}; + +/** + * 消息渲染器 + */ +const MessageRenderer = ({ message, userAvatar }) => { + switch (message.type) { + case MessageTypes.USER: + return ( + + + + + + + {message.content} + + {message.files && message.files.length > 0 && ( + + {message.files.map((file, idx) => ( + + + {file.name} + + ))} + + )} + + + + } + size="sm" + bgGradient="linear(to-br, blue.500, purple.600)" + boxShadow="0 0 12px rgba(139, 92, 246, 0.4)" + /> + + + ); + + case MessageTypes.AGENT_THINKING: + return ( + + + } + size="sm" + bgGradient="linear(to-br, purple.500, pink.500)" + boxShadow="0 0 12px rgba(236, 72, 153, 0.4)" + /> + + + + + + {message.content} + + + + + + + ); + + case MessageTypes.AGENT_RESPONSE: + return ( + + + } + size="sm" + bgGradient="linear(to-br, purple.500, pink.500)" + boxShadow="0 0 12px rgba(236, 72, 153, 0.4)" + /> + + + + + {message.content} + + + {message.stepResults && message.stepResults.length > 0 && ( + + + + )} + + + + + } + onClick={() => navigator.clipboard.writeText(message.content)} + bg="rgba(255, 255, 255, 0.05)" + color="gray.400" + _hover={{ + color: "white", + bg: "rgba(255, 255, 255, 0.1)" + }} + /> + + + + + } + bg="rgba(255, 255, 255, 0.05)" + color="gray.400" + _hover={{ + color: "green.400", + bg: "rgba(16, 185, 129, 0.1)", + boxShadow: "0 0 12px rgba(16, 185, 129, 0.3)" + }} + /> + + + + + } + bg="rgba(255, 255, 255, 0.05)" + color="gray.400" + _hover={{ + color: "red.400", + bg: "rgba(239, 68, 68, 0.1)", + boxShadow: "0 0 12px rgba(239, 68, 68, 0.3)" + }} + /> + + + + {new Date(message.timestamp).toLocaleTimeString('zh-CN', { + hour: '2-digit', + minute: '2-digit', + })} + + + + + + + + ); + + case MessageTypes.ERROR: + return ( + + + + + {message.content} + + + + + ); + + default: + return null; + } +}; + +/** + * 执行步骤显示组件 + */ +const ExecutionStepsDisplay = ({ steps, plan }) => { + return ( + + + + + + 执行详情 + + {steps.length} 步骤 + + + + + + + {steps.map((result, idx) => ( + + + + + + 步骤 {idx + 1}: {result.tool_name} + + + {result.status} + + + + {result.execution_time?.toFixed(2)}s + + {result.error && ( + ⚠️ {result.error} + )} + + + + ))} + + + + + ); +}; diff --git a/src/views/AgentChat/index.js.bak2 b/src/views/AgentChat/index.js.bak2 new file mode 100644 index 00000000..071ce7a6 --- /dev/null +++ b/src/views/AgentChat/index.js.bak2 @@ -0,0 +1,1173 @@ +// src/views/AgentChat/index.js +// 超炫酷的 AI 投研助手 - HeroUI v3 现代深色主题版本 +// 使用 Framer Motion 物理动画引擎 + 毛玻璃效果 + +import React, { useState, useEffect, useRef } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { + Box, + Button, + Input, + Avatar, + Badge, + Divider, + Spinner, + Tooltip, + Checkbox, + CheckboxGroup, + Kbd, + Accordion, + AccordionItem, + AccordionButton, + AccordionPanel, + AccordionIcon, + Tabs, + TabList, + TabPanels, + Tab, + TabPanel, + useToast, + VStack, + HStack, + Text, + Flex, + IconButton, + useColorMode, + Card, + CardBody, + Tag, + TagLabel, + TagCloseButton, +} from '@chakra-ui/react'; +import { useAuth } from '@contexts/AuthContext'; +import { logger } from '@utils/logger'; +import axios from 'axios'; + +// 图标 - 使用 Lucide Icons +import { + Send, + Plus, + Search, + MessageSquare, + Trash2, + MoreVertical, + RefreshCw, + Download, + Cpu, + User, + Zap, + Clock, + Settings, + ChevronLeft, + ChevronRight, + Activity, + Code, + Database, + TrendingUp, + FileText, + BookOpen, + Menu, + X, + Check, + Circle, + Maximize2, + Minimize2, + Copy, + ThumbsUp, + ThumbsDown, + Sparkles, + Brain, + Rocket, + Paperclip, + Image as ImageIcon, + File, + Calendar, + Globe, + DollarSign, + Newspaper, + BarChart3, + PieChart, + LineChart, + Briefcase, + Users, +} from 'lucide-react'; + +// 常量配置 - 从 TypeScript 模块导入 +import { MessageTypes } from './constants/messageTypes'; +import { DEFAULT_MODEL_ID } from './constants/models'; +import { DEFAULT_SELECTED_TOOLS } from './constants/tools'; + +// 拆分后的子组件 +import BackgroundEffects from './components/BackgroundEffects'; +import LeftSidebar from './components/LeftSidebar'; +import ChatArea from './components/ChatArea'; +import RightSidebar from './components/RightSidebar'; + +/** + * Agent Chat - 主组件(HeroUI v3 深色主题) + * + * 注意:所有常量配置已提取到 constants/ 目录: + * - animations: constants/animations.ts + * - MessageTypes: constants/messageTypes.ts + * - AVAILABLE_MODELS: constants/models.ts + * - MCP_TOOLS, TOOL_CATEGORIES: constants/tools.ts + * - quickQuestions: constants/quickQuestions.ts + */ +const AgentChat = () => { + const { user } = useAuth(); + const toast = useToast(); + const { setColorMode } = useColorMode(); + + // 会话管理 + const [sessions, setSessions] = useState([]); + const [currentSessionId, setCurrentSessionId] = useState(null); + const [isLoadingSessions, setIsLoadingSessions] = useState(false); + + // 消息管理 + const [messages, setMessages] = useState([]); + const [inputValue, setInputValue] = useState(''); + const [isProcessing, setIsProcessing] = useState(false); + + // UI 状态 + const [searchQuery, setSearchQuery] = useState(''); + const [selectedModel, setSelectedModel] = useState(DEFAULT_MODEL_ID); + const [selectedTools, setSelectedTools] = useState(DEFAULT_SELECTED_TOOLS); + const [isLeftSidebarOpen, setIsLeftSidebarOpen] = useState(true); + const [isRightSidebarOpen, setIsRightSidebarOpen] = useState(true); + + // 文件上传 + const [uploadedFiles, setUploadedFiles] = useState([]); + const fileInputRef = useRef(null); + + // Refs + const messagesEndRef = useRef(null); + const inputRef = useRef(null); + + // ==================== 启用深色模式 ==================== + useEffect(() => { + // 为 AgentChat 页面强制启用深色模式 + setColorMode('dark'); + document.documentElement.classList.add('dark'); + + return () => { + // 组件卸载时不移除,让其他页面自己控制 + // document.documentElement.classList.remove('dark'); + }; + }, [setColorMode]); + + // ==================== API 调用函数 ==================== + + 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); + } + } catch (error) { + logger.error('加载会话列表失败', error); + } 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); + } + } catch (error) { + logger.error('加载会话历史失败', error); + } + }; + + const createNewSession = () => { + setCurrentSessionId(null); + setMessages([ + { + id: Date.now(), + type: MessageTypes.AGENT_RESPONSE, + content: `你好${user?.nickname || ''}!👋\n\n我是**价小前**,你的 AI 投研助手。\n\n**我能做什么?**\n• 📊 全面分析股票基本面和技术面\n• 🔥 追踪市场热点和涨停板块\n• 📈 研究行业趋势和投资机会\n• 📰 汇总最新财经新闻和研报\n\n直接输入你的问题开始探索!`, + timestamp: new Date().toISOString(), + }, + ]); + }; + + const switchSession = (sessionId) => { + setCurrentSessionId(sessionId); + loadSessionHistory(sessionId); + }; + + const handleSendMessage = async () => { + if (!inputValue.trim() || isProcessing) return; + + const userMessage = { + type: MessageTypes.USER, + content: inputValue, + timestamp: new Date().toISOString(), + files: uploadedFiles.length > 0 ? uploadedFiles : undefined, + }; + + addMessage(userMessage); + const userInput = inputValue; + setInputValue(''); + setUploadedFiles([]); + setIsProcessing(true); + + try { + addMessage({ + type: MessageTypes.AGENT_THINKING, + content: '正在分析你的问题...', + timestamp: new Date().toISOString(), + }); + + 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 || '', + subscription_type: user?.subscription_type || 'free', + session_id: currentSessionId, + model: selectedModel, + tools: selectedTools, + files: uploadedFiles.length > 0 ? uploadedFiles : undefined, + }); + + setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_THINKING)); + + if (response.data.success) { + const data = response.data; + if (data.session_id && !currentSessionId) { + setCurrentSessionId(data.session_id); + } + + if (data.plan) { + addMessage({ + type: MessageTypes.AGENT_PLAN, + content: '已制定执行计划', + plan: data.plan, + timestamp: new Date().toISOString(), + }); + } + + if (data.steps && data.steps.length > 0) { + addMessage({ + type: MessageTypes.AGENT_EXECUTING, + content: '正在执行步骤...', + plan: data.plan, + stepResults: data.steps, + timestamp: new Date().toISOString(), + }); + } + + setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_EXECUTING)); + + addMessage({ + type: MessageTypes.AGENT_RESPONSE, + content: data.final_answer || data.message || '处理完成', + plan: data.plan, + stepResults: data.steps, + metadata: data.metadata, + timestamp: new Date().toISOString(), + }); + + 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, + }); + } finally { + setIsProcessing(false); + } + }; + + // 文件上传处理 + const handleFileSelect = (event) => { + const files = Array.from(event.target.files || []); + const fileData = files.map(file => ({ + name: file.name, + size: file.size, + type: file.type, + // 实际上传时需要转换为 base64 或上传到服务器 + url: URL.createObjectURL(file), + })); + setUploadedFiles(prev => [...prev, ...fileData]); + }; + + const removeFile = (index) => { + setUploadedFiles(prev => prev.filter((_, i) => i !== index)); + }; + + const addMessage = (message) => { + setMessages((prev) => [ + ...prev, + { + id: Date.now() + Math.random(), + ...message, + }, + ]); + }; + + const handleKeyPress = (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSendMessage(); + } + }; + + useEffect(() => { + loadSessions(); + createNewSession(); + }, [user]); + + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages]); + + return ( + + + {/* 背景渐变装饰 */} + + + {/* 左侧栏 */} + setIsLeftSidebarOpen(false)} + sessions={sessions} + currentSessionId={currentSessionId} + onSessionSwitch={switchSession} + onNewSession={createNewSession} + isLoadingSessions={isLoadingSessions} + user={user} + /> + + {/* 中间主聊天区域 */} + + {/* 顶部标题栏 - 深色毛玻璃 */} + + + + {!isLeftSidebarOpen && ( + + } + onClick={() => setIsLeftSidebarOpen(true)} + bg="rgba(255, 255, 255, 0.05)" + color="gray.400" + backdropFilter="blur(10px)" + border="1px solid" + borderColor="rgba(255, 255, 255, 0.1)" + _hover={{ + bg: "rgba(255, 255, 255, 0.1)", + color: "white" + }} + /> + + )} + + + } + bgGradient="linear(to-br, purple.500, pink.500)" + boxShadow="0 0 20px rgba(236, 72, 153, 0.5)" + /> + + + + + 价小前投研 AI + + + + + 智能分析 + + + {AVAILABLE_MODELS.find((m) => m.id === selectedModel)?.name} + + + + + + + + + } + onClick={createNewSession} + bg="rgba(255, 255, 255, 0.05)" + color="gray.400" + backdropFilter="blur(10px)" + border="1px solid" + borderColor="rgba(255, 255, 255, 0.1)" + _hover={{ + bg: "rgba(255, 255, 255, 0.1)", + color: "white", + borderColor: "purple.400", + boxShadow: "0 0 12px rgba(139, 92, 246, 0.3)" + }} + /> + + + {!isRightSidebarOpen && ( + + } + onClick={() => setIsRightSidebarOpen(true)} + bg="rgba(255, 255, 255, 0.05)" + color="gray.400" + backdropFilter="blur(10px)" + border="1px solid" + borderColor="rgba(255, 255, 255, 0.1)" + _hover={{ + bg: "rgba(255, 255, 255, 0.1)", + color: "white" + }} + /> + + )} + + + + + {/* 消息列表 */} + + + + + {messages.map((message) => ( + + + + ))} + +
+ + + + + {/* 快捷问题 */} + + {messages.length <= 2 && !isProcessing && ( + + + + + + 快速开始 + + + {quickQuestions.map((question, idx) => ( + + + + ))} + + + + + )} + + + {/* 输入栏 - 深色毛玻璃 */} + + + {/* 已上传文件预览 */} + {uploadedFiles.length > 0 && ( + + {uploadedFiles.map((file, idx) => ( + + + {file.name} + removeFile(idx)} color="gray.400" /> + + + ))} + + )} + + + + + + + } + onClick={() => fileInputRef.current?.click()} + bg="rgba(255, 255, 255, 0.05)" + color="gray.300" + backdropFilter="blur(10px)" + border="1px solid" + borderColor="rgba(255, 255, 255, 0.1)" + _hover={{ + bg: "rgba(255, 255, 255, 0.1)", + borderColor: "purple.400", + color: "white", + boxShadow: "0 0 12px rgba(139, 92, 246, 0.3)" + }} + /> + + + + + + } + onClick={() => { + fileInputRef.current?.setAttribute('accept', 'image/*'); + fileInputRef.current?.click(); + }} + bg="rgba(255, 255, 255, 0.05)" + color="gray.300" + backdropFilter="blur(10px)" + border="1px solid" + borderColor="rgba(255, 255, 255, 0.1)" + _hover={{ + bg: "rgba(255, 255, 255, 0.1)", + borderColor: "purple.400", + color: "white", + boxShadow: "0 0 12px rgba(139, 92, 246, 0.3)" + }} + /> + + + + setInputValue(e.target.value)} + onKeyDown={handleKeyPress} + placeholder="输入你的问题... (Enter 发送, Shift+Enter 换行)" + isDisabled={isProcessing} + size="lg" + variant="outline" + borderWidth={2} + bg="rgba(255, 255, 255, 0.05)" + backdropFilter="blur(10px)" + 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: "purple.400", + boxShadow: "0 0 0 1px var(--chakra-colors-purple-400), 0 0 12px rgba(139, 92, 246, 0.3)", + bg: "rgba(255, 255, 255, 0.08)" + }} + /> + + + } + onClick={handleSendMessage} + isLoading={isProcessing} + isDisabled={!inputValue.trim() || isProcessing} + bgGradient="linear(to-r, blue.500, purple.600)" + color="white" + _hover={{ + bgGradient: "linear(to-r, blue.600, purple.700)", + boxShadow: "0 8px 20px rgba(139, 92, 246, 0.4)" + }} + _active={{ + transform: "translateY(0)", + boxShadow: "0 4px 12px rgba(139, 92, 246, 0.3)" + }} + /> + + + + + + Enter + 发送 + + + Shift + + + Enter + 换行 + + + + + + + {/* 右侧栏 - 深色配置中心 */} + + {isRightSidebarOpen && ( + + + + + + + + 配置中心 + + + + + } + onClick={() => setIsRightSidebarOpen(false)} + bg="rgba(255, 255, 255, 0.05)" + color="gray.300" + backdropFilter="blur(10px)" + border="1px solid" + borderColor="rgba(255, 255, 255, 0.1)" + _hover={{ + bg: "rgba(255, 255, 255, 0.1)", + borderColor: "purple.400", + color: "white" + }} + /> + + + + + + + + + + + + 模型 + + + + + + 工具 + {selectedTools.length > 0 && ( + + {selectedTools.length} + + )} + + + + + + 统计 + + + + + + {/* 模型选择 */} + + + {AVAILABLE_MODELS.map((model, idx) => ( + + setSelectedModel(model.id)} + bg={selectedModel === model.id + ? 'rgba(139, 92, 246, 0.15)' + : 'rgba(255, 255, 255, 0.05)'} + backdropFilter="blur(12px)" + borderWidth={2} + borderColor={selectedModel === model.id + ? 'purple.400' + : 'rgba(255, 255, 255, 0.1)'} + _hover={{ + borderColor: selectedModel === model.id + ? 'purple.400' + : 'rgba(255, 255, 255, 0.2)', + boxShadow: selectedModel === model.id + ? "0 8px 20px rgba(139, 92, 246, 0.4)" + : "0 4px 12px rgba(0, 0, 0, 0.3)" + }} + transition="all 0.3s" + > + + + + {model.icon} + + + + {model.name} + + + {model.description} + + + {selectedModel === model.id && ( + + + + )} + + + + + ))} + + + + {/* 工具选择 */} + + + {Object.entries(TOOL_CATEGORIES).map(([category, tools], catIdx) => ( + + + + + {category} + + {tools.filter(t => selectedTools.includes(t.id)).length}/{tools.length} + + + + + + + + {tools.map((tool) => ( + + + + {tool.icon} + + {tool.name} + {tool.description} + + + + + ))} + + + + + + ))} + + + + + + + + + + + + + {/* 统计信息 */} + + + + + + + + 对话数 + + {sessions.length} + + + + + + + + + + + + + + 消息数 + + {messages.length} + + + + + + + + + + + + + + 已选工具 + + {selectedTools.length} + + + + + + + + + + + + + + + )} + + + + ); +}; + +export default AgentChat; diff --git a/src/views/AgentChat/index.js.bak3 b/src/views/AgentChat/index.js.bak3 new file mode 100644 index 00000000..324d7229 --- /dev/null +++ b/src/views/AgentChat/index.js.bak3 @@ -0,0 +1,816 @@ +// src/views/AgentChat/index.js +// 超炫酷的 AI 投研助手 - HeroUI v3 现代深色主题版本 +// 使用 Framer Motion 物理动画引擎 + 毛玻璃效果 + +import React, { useState, useEffect, useRef } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { + Box, + Button, + Input, + Avatar, + Badge, + Divider, + Spinner, + Tooltip, + Checkbox, + CheckboxGroup, + Kbd, + Accordion, + AccordionItem, + AccordionButton, + AccordionPanel, + AccordionIcon, + Tabs, + TabList, + TabPanels, + Tab, + TabPanel, + useToast, + VStack, + HStack, + Text, + Flex, + IconButton, + useColorMode, + Card, + CardBody, + Tag, + TagLabel, + TagCloseButton, +} from '@chakra-ui/react'; +import { useAuth } from '@contexts/AuthContext'; +import { logger } from '@utils/logger'; +import axios from 'axios'; + +// 图标 - 使用 Lucide Icons +import { + Send, + Plus, + Search, + MessageSquare, + Trash2, + MoreVertical, + RefreshCw, + Download, + Cpu, + User, + Zap, + Clock, + Settings, + ChevronLeft, + ChevronRight, + Activity, + Code, + Database, + TrendingUp, + FileText, + BookOpen, + Menu, + X, + Check, + Circle, + Maximize2, + Minimize2, + Copy, + ThumbsUp, + ThumbsDown, + Sparkles, + Brain, + Rocket, + Paperclip, + Image as ImageIcon, + File, + Calendar, + Globe, + DollarSign, + Newspaper, + BarChart3, + PieChart, + LineChart, + Briefcase, + Users, +} from 'lucide-react'; + +// 常量配置 - 从 TypeScript 模块导入 +import { MessageTypes } from './constants/messageTypes'; +import { DEFAULT_MODEL_ID } from './constants/models'; +import { DEFAULT_SELECTED_TOOLS } from './constants/tools'; + +// 拆分后的子组件 +import BackgroundEffects from './components/BackgroundEffects'; +import LeftSidebar from './components/LeftSidebar'; +import ChatArea from './components/ChatArea'; +import RightSidebar from './components/RightSidebar'; + +/** + * Agent Chat - 主组件(HeroUI v3 深色主题) + * + * 注意:所有常量配置已提取到 constants/ 目录: + * - animations: constants/animations.ts + * - MessageTypes: constants/messageTypes.ts + * - AVAILABLE_MODELS: constants/models.ts + * - MCP_TOOLS, TOOL_CATEGORIES: constants/tools.ts + * - quickQuestions: constants/quickQuestions.ts + */ +const AgentChat = () => { + const { user } = useAuth(); + const toast = useToast(); + const { setColorMode } = useColorMode(); + + // 会话管理 + const [sessions, setSessions] = useState([]); + const [currentSessionId, setCurrentSessionId] = useState(null); + const [isLoadingSessions, setIsLoadingSessions] = useState(false); + + // 消息管理 + const [messages, setMessages] = useState([]); + const [inputValue, setInputValue] = useState(''); + const [isProcessing, setIsProcessing] = useState(false); + + // UI 状态 + const [searchQuery, setSearchQuery] = useState(''); + const [selectedModel, setSelectedModel] = useState(DEFAULT_MODEL_ID); + const [selectedTools, setSelectedTools] = useState(DEFAULT_SELECTED_TOOLS); + const [isLeftSidebarOpen, setIsLeftSidebarOpen] = useState(true); + const [isRightSidebarOpen, setIsRightSidebarOpen] = useState(true); + + // 文件上传 + const [uploadedFiles, setUploadedFiles] = useState([]); + const fileInputRef = useRef(null); + + // Refs + const messagesEndRef = useRef(null); + const inputRef = useRef(null); + + // ==================== 启用深色模式 ==================== + useEffect(() => { + // 为 AgentChat 页面强制启用深色模式 + setColorMode('dark'); + document.documentElement.classList.add('dark'); + + return () => { + // 组件卸载时不移除,让其他页面自己控制 + // document.documentElement.classList.remove('dark'); + }; + }, [setColorMode]); + + // ==================== API 调用函数 ==================== + + 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); + } + } catch (error) { + logger.error('加载会话列表失败', error); + } 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); + } + } catch (error) { + logger.error('加载会话历史失败', error); + } + }; + + const createNewSession = () => { + setCurrentSessionId(null); + setMessages([ + { + id: Date.now(), + type: MessageTypes.AGENT_RESPONSE, + content: `你好${user?.nickname || ''}!👋\n\n我是**价小前**,你的 AI 投研助手。\n\n**我能做什么?**\n• 📊 全面分析股票基本面和技术面\n• 🔥 追踪市场热点和涨停板块\n• 📈 研究行业趋势和投资机会\n• 📰 汇总最新财经新闻和研报\n\n直接输入你的问题开始探索!`, + timestamp: new Date().toISOString(), + }, + ]); + }; + + const switchSession = (sessionId) => { + setCurrentSessionId(sessionId); + loadSessionHistory(sessionId); + }; + + const handleSendMessage = async () => { + if (!inputValue.trim() || isProcessing) return; + + const userMessage = { + type: MessageTypes.USER, + content: inputValue, + timestamp: new Date().toISOString(), + files: uploadedFiles.length > 0 ? uploadedFiles : undefined, + }; + + addMessage(userMessage); + const userInput = inputValue; + setInputValue(''); + setUploadedFiles([]); + setIsProcessing(true); + + try { + addMessage({ + type: MessageTypes.AGENT_THINKING, + content: '正在分析你的问题...', + timestamp: new Date().toISOString(), + }); + + 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 || '', + subscription_type: user?.subscription_type || 'free', + session_id: currentSessionId, + model: selectedModel, + tools: selectedTools, + files: uploadedFiles.length > 0 ? uploadedFiles : undefined, + }); + + setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_THINKING)); + + if (response.data.success) { + const data = response.data; + if (data.session_id && !currentSessionId) { + setCurrentSessionId(data.session_id); + } + + if (data.plan) { + addMessage({ + type: MessageTypes.AGENT_PLAN, + content: '已制定执行计划', + plan: data.plan, + timestamp: new Date().toISOString(), + }); + } + + if (data.steps && data.steps.length > 0) { + addMessage({ + type: MessageTypes.AGENT_EXECUTING, + content: '正在执行步骤...', + plan: data.plan, + stepResults: data.steps, + timestamp: new Date().toISOString(), + }); + } + + setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_EXECUTING)); + + addMessage({ + type: MessageTypes.AGENT_RESPONSE, + content: data.final_answer || data.message || '处理完成', + plan: data.plan, + stepResults: data.steps, + metadata: data.metadata, + timestamp: new Date().toISOString(), + }); + + 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, + }); + } finally { + setIsProcessing(false); + } + }; + + // 文件上传处理 + const handleFileSelect = (event) => { + const files = Array.from(event.target.files || []); + const fileData = files.map(file => ({ + name: file.name, + size: file.size, + type: file.type, + // 实际上传时需要转换为 base64 或上传到服务器 + url: URL.createObjectURL(file), + })); + setUploadedFiles(prev => [...prev, ...fileData]); + }; + + const removeFile = (index) => { + setUploadedFiles(prev => prev.filter((_, i) => i !== index)); + }; + + const addMessage = (message) => { + setMessages((prev) => [ + ...prev, + { + id: Date.now() + Math.random(), + ...message, + }, + ]); + }; + + const handleKeyPress = (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSendMessage(); + } + }; + + useEffect(() => { + loadSessions(); + createNewSession(); + }, [user]); + + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages]); + + return ( + + + {/* 背景渐变装饰 */} + + + {/* 左侧栏 */} + setIsLeftSidebarOpen(false)} + sessions={sessions} + currentSessionId={currentSessionId} + onSessionSwitch={switchSession} + onNewSession={createNewSession} + isLoadingSessions={isLoadingSessions} + user={user} + /> + + {/* 中间聊天区 */} + setIsLeftSidebarOpen(true)} + onToggleRightSidebar={() => setIsRightSidebarOpen(true)} + onNewSession={createNewSession} + userAvatar={user?.avatar} + messagesEndRef={messagesEndRef} + inputRef={inputRef} + fileInputRef={fileInputRef} + /> + + {/* 右侧栏 - 深色配置中心 */} + + {isRightSidebarOpen && ( + + + + + + + + 配置中心 + + + + + } + onClick={() => setIsRightSidebarOpen(false)} + bg="rgba(255, 255, 255, 0.05)" + color="gray.300" + backdropFilter="blur(10px)" + border="1px solid" + borderColor="rgba(255, 255, 255, 0.1)" + _hover={{ + bg: "rgba(255, 255, 255, 0.1)", + borderColor: "purple.400", + color: "white" + }} + /> + + + + + + + + + + + + 模型 + + + + + + 工具 + {selectedTools.length > 0 && ( + + {selectedTools.length} + + )} + + + + + + 统计 + + + + + + {/* 模型选择 */} + + + {AVAILABLE_MODELS.map((model, idx) => ( + + setSelectedModel(model.id)} + bg={selectedModel === model.id + ? 'rgba(139, 92, 246, 0.15)' + : 'rgba(255, 255, 255, 0.05)'} + backdropFilter="blur(12px)" + borderWidth={2} + borderColor={selectedModel === model.id + ? 'purple.400' + : 'rgba(255, 255, 255, 0.1)'} + _hover={{ + borderColor: selectedModel === model.id + ? 'purple.400' + : 'rgba(255, 255, 255, 0.2)', + boxShadow: selectedModel === model.id + ? "0 8px 20px rgba(139, 92, 246, 0.4)" + : "0 4px 12px rgba(0, 0, 0, 0.3)" + }} + transition="all 0.3s" + > + + + + {model.icon} + + + + {model.name} + + + {model.description} + + + {selectedModel === model.id && ( + + + + )} + + + + + ))} + + + + {/* 工具选择 */} + + + {Object.entries(TOOL_CATEGORIES).map(([category, tools], catIdx) => ( + + + + + {category} + + {tools.filter(t => selectedTools.includes(t.id)).length}/{tools.length} + + + + + + + + {tools.map((tool) => ( + + + + {tool.icon} + + {tool.name} + {tool.description} + + + + + ))} + + + + + + ))} + + + + + + + + + + + + + {/* 统计信息 */} + + + + + + + + 对话数 + + {sessions.length} + + + + + + + + + + + + + + 消息数 + + {messages.length} + + + + + + + + + + + + + + 已选工具 + + {selectedTools.length} + + + + + + + + + + + + + + + )} + + + + ); +}; + +export default AgentChat;