diff --git a/package.json b/package.json index 6a33b14d..9a97eb5a 100755 --- a/package.json +++ b/package.json @@ -18,8 +18,6 @@ "@fullcalendar/daygrid": "^6.1.19", "@fullcalendar/interaction": "^6.1.19", "@fullcalendar/react": "^6.1.19", - "@heroui/react": "^3.0.0-beta.2", - "@heroui/styles": "^3.0.0-beta.2", "@reduxjs/toolkit": "^2.9.2", "@splidejs/react-splide": "^0.7.12", "@tanstack/react-virtual": "^3.13.12", @@ -112,7 +110,6 @@ }, "devDependencies": { "@craco/craco": "^7.1.0", - "@tailwindcss/postcss": "next", "@types/node": "^20.19.25", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", @@ -133,7 +130,6 @@ "prettier": "2.2.1", "react-error-overlay": "6.0.9", "sharp": "^0.34.4", - "tailwindcss": "next", "ts-node": "^10.9.2", "webpack-bundle-analyzer": "^4.10.2", "yn": "^5.1.0" diff --git a/postcss.config.js b/postcss.config.js deleted file mode 100644 index fa7d5e7d..00000000 --- a/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -// PostCSS 配置 - Tailwind CSS v4 -module.exports = { - plugins: { - '@tailwindcss/postcss': {}, - }, -} diff --git a/src/index.js b/src/index.js index 5c4d0027..0c8db5ae 100755 --- a/src/index.js +++ b/src/index.js @@ -3,9 +3,6 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import { BrowserRouter as Router } from 'react-router-dom'; -// 导入 HeroUI v3 样式(必须在最前面导入,包含 Tailwind CSS) -import './styles/heroui.css'; - // 导入 Brainwave 样式(空文件,保留以避免错误) import './styles/brainwave.css'; diff --git a/src/styles/heroui.css b/src/styles/heroui.css deleted file mode 100644 index b9acd7df..00000000 --- a/src/styles/heroui.css +++ /dev/null @@ -1,10 +0,0 @@ -/** - * HeroUI v3 + Tailwind CSS v4 配置 - * 参考文档: https://v3.heroui.com/docs/quick-start - * - * 重要:HeroUI v3 beta 需要 Tailwind v4 - * 导入顺序:先 tailwindcss,后 @heroui/styles - */ - -@import "tailwindcss"; -@import "@heroui/styles"; diff --git a/src/views/AgentChat/index.js b/src/views/AgentChat/index.js index 468c504c..c79e70c6 100644 --- a/src/views/AgentChat/index.js +++ b/src/views/AgentChat/index.js @@ -1,30 +1,43 @@ // src/views/AgentChat/index.js -// 超炫酷的 AI 投研助手 - Hero UI 深色模式版本 +// 超炫酷的 AI 投研助手 - Chakra UI 深色模式版本 // 使用 Framer Motion 物理动画引擎 import React, { useState, useEffect, useRef } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { + Box, Button, - Card, Input, Avatar, - Chip, - Separator, + Badge, + Divider, Spinner, Tooltip, Checkbox, CheckboxGroup, Kbd, Accordion, -} from '@heroui/react'; -import { + AccordionItem, + AccordionButton, + AccordionPanel, + AccordionIcon, Tabs, TabList, TabPanels, Tab, TabPanel, - useToast + 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'; @@ -165,23 +178,23 @@ const AVAILABLE_MODELS = [ { id: 'kimi-k2-thinking', name: 'Kimi K2 Thinking', - description: '深度思考模型,适合复杂分析', + description: '深度思考模型,适合复杂分析', icon: , - color: 'secondary', + color: 'purple', }, { id: 'kimi-k2', name: 'Kimi K2', - description: '快速响应模型,适合简单查询', + description: '快速响应模型,适合简单查询', icon: , - color: 'primary', + color: 'blue', }, { id: 'deepmoney', name: 'DeepMoney', description: '金融专业模型', icon: , - color: 'success', + color: 'green', }, ]; @@ -195,7 +208,7 @@ const MCP_TOOLS = [ name: '全球新闻搜索', icon: , category: '新闻资讯', - description: '搜索全球新闻,支持关键词和日期过滤' + description: '搜索全球新闻,支持关键词和日期过滤' }, { id: 'search_china_news', @@ -248,7 +261,7 @@ const MCP_TOOLS = [ name: '涨停股票搜索', icon: , category: '涨停分析', - description: '搜索涨停股票,支持多条件筛选' + description: '搜索涨停股票,支持多条件筛选' }, { id: 'get_daily_stock_analysis', @@ -264,7 +277,7 @@ const MCP_TOOLS = [ name: '研报搜索', icon: , category: '研报路演', - description: '搜索研究报告,支持语义搜索' + description: '搜索研究报告,支持语义搜索' }, { id: 'search_roadshows', @@ -353,11 +366,12 @@ const TOOL_CATEGORIES = { }; /** - * Hero Agent Chat - 主组件(深色模式) + * Agent Chat - 主组件(深色模式) */ const AgentChat = () => { const { user } = useAuth(); const toast = useToast(); + const { setColorMode } = useColorMode(); // 会话管理 const [sessions, setSessions] = useState([]); @@ -393,13 +407,14 @@ const AgentChat = () => { // ==================== 启用深色模式 ==================== useEffect(() => { // 为 AgentChat 页面强制启用深色模式 + setColorMode('dark'); document.documentElement.classList.add('dark'); return () => { - // 组件卸载时不移除,让其他页面自己控制 + // 组件卸载时不移除,让其他页面自己控制 // document.documentElement.classList.remove('dark'); }; - }, []); + }, [setColorMode]); // ==================== API 调用函数 ==================== @@ -449,7 +464,7 @@ const AgentChat = () => { { id: Date.now(), type: MessageTypes.AGENT_RESPONSE, - content: `你好${user?.nickname || ''}!👋\n\n我是**价小前**,你的 AI 投研助手。\n\n**我能做什么?**\n• 📊 全面分析股票基本面和技术面\n• 🔥 追踪市场热点和涨停板块\n• 📈 研究行业趋势和投资机会\n• 📰 汇总最新财经新闻和研报\n\n直接输入你的问题开始探索!`, + content: `你好${user?.nickname || ''}!👋\n\n我是**价小前**,你的 AI 投研助手。\n\n**我能做什么?**\n• 📊 全面分析股票基本面和技术面\n• 🔥 追踪市场热点和涨停板块\n• 📈 研究行业趋势和投资机会\n• 📰 汇总最新财经新闻和研报\n\n直接输入你的问题开始探索!`, timestamp: new Date().toISOString(), }, ]); @@ -659,176 +674,200 @@ const AgentChat = () => { ]; return ( -
+ {/* 左侧栏 - 深色毛玻璃 */} - {isLeftSidebarOpen && ( - -
-
-
- - 对话历史 -
-
- - - - - - -
-
- - } - size="sm" - variant="bordered" - classNames={{ - input: 'text-sm text-gray-100', - inputWrapper: 'border-gray-700 bg-gray-800/50 hover:border-gray-600', - }} - /> -
- -
- {/* 按日期分组显示会话 */} - {sessionGroups.today.length > 0 && ( -
-

今天

-
- {sessionGroups.today.map((session) => ( - 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'} 用户

-
-
-
-
- )} + bgGradient="linear(to-br, blue.500, purple.600)" + /> + + + {user?.nickname || '未登录'} + + + {user?.subscription_type || 'free'} 用户 + + + + + + + )} + {/* 中间主聊天区域 */} -
+ {/* 顶部标题栏 - 深色 */} -
-
-
+ + + {!isLeftSidebarOpen && ( - + variant="ghost" + icon={} + onClick={() => setIsLeftSidebarOpen(true)} + color="gray.400" + /> )} { > } - classNames={{ - base: 'bg-gradient-to-br from-purple-500 to-pink-500', - }} + bgGradient="linear(to-br, purple.500, pink.500)" /> -
-

+ + 价小前投研 AI -

-
- } - classNames={{ - base: 'bg-green-500/20', - content: 'text-green-400', - }} + + + + 智能分析 - - + {AVAILABLE_MODELS.find((m) => m.id === selectedModel)?.name} - -
-
-
+ + + + -
- - + variant="ghost" + icon={} + onClick={createNewSession} + color="gray.400" + _hover={{ color: 'gray.200' }} + /> {!isRightSidebarOpen && ( - + variant="ghost" + icon={} + onClick={() => setIsRightSidebarOpen(true)} + color="gray.400" + _hover={{ color: 'gray.200' }} + /> )} -
-
-
+ +
+ {/* 消息列表 */} -
+ - - {messages.map((message, index) => ( - - - - ))} - -
+ + + {messages.map((message) => ( + + + + ))} + +
+ -
+ {/* 快捷问题 */} {messages.length <= 2 && !isProcessing && ( -
-

- - 快速开始 -

-
- {quickQuestions.map((question, idx) => ( - - - - ))} -
-
+ + + + + 快速开始 + + + {quickQuestions.map((question, idx) => ( + + + + ))} + + +
)}
{/* 输入栏 - 深色 */} -
-
+ + {/* 已上传文件预览 */} {uploadedFiles.length > 0 && ( -
+ {uploadedFiles.map((file, idx) => ( - removeFile(idx)} - variant="flat" - classNames={{ - base: 'bg-gray-800 border border-gray-700', - content: 'text-gray-300', - }} + size="md" + variant="subtle" + bg="gray.700" + borderColor="gray.600" + borderWidth={1} > - {file.name} - + {file.name} + removeFile(idx)} /> + ))} -
+ )} -
+ - - + icon={} + onClick={() => fileInputRef.current?.click()} + bg="gray.700" + color="gray.300" + _hover={{ bg: 'gray.600' }} + /> - - + bg="gray.700" + color="gray.300" + _hover={{ bg: 'gray.600' }} + /> setInputValue(e.target.value)} onKeyDown={handleKeyPress} placeholder="输入你的问题... (Enter 发送, Shift+Enter 换行)" - disabled={isProcessing} + isDisabled={isProcessing} size="lg" - variant="bordered" - classNames={{ - input: 'text-base text-gray-100', - inputWrapper: 'border-2 border-gray-700 bg-gray-800/50 hover:border-blue-500 focus-within:border-blue-500', - }} + variant="outline" + borderWidth={2} + borderColor="gray.600" + bg="rgba(31, 41, 55, 0.5)" + color="gray.100" + _hover={{ borderColor: 'blue.500' }} + _focus={{ borderColor: 'blue.500', boxShadow: '0 0 0 1px var(--chakra-colors-blue-500)' }} /> - + bgGradient="linear(to-r, blue.600, purple.600)" + _hover={{ bgGradient: 'linear(to-r, blue.500, purple.500)' }} + /> -
+ -
-
- Enter - 发送 -
-
- Shift - + - Enter - 换行 -
-
-
-
-
+ + + Enter + 发送 + + + Shift + + + Enter + 换行 + + +
+ + {/* 右侧栏 - 深色配置中心 */} - {isRightSidebarOpen && ( - -
-
-
- - 配置中心 -
- - - -
-
- -
- + {isRightSidebarOpen && ( + + - {/* 模型选择 */} - - - 模型 -
- } - > -
- {AVAILABLE_MODELS.map((model) => ( - setSelectedModel(model.id)} - className={`transition-all ${ - selectedModel === model.id - ? 'bg-blue-500/20 border-2 border-blue-500' - : 'bg-gray-800/50 border-2 border-gray-700 hover:border-gray-600' - }`} - > - -
-
- {model.icon} -
-
-

{model.name}

-

{model.description}

-
- {selectedModel === model.id && ( - - )} -
-
-
- ))} -
- + + + + + 配置中心 + + + } + onClick={() => setIsRightSidebarOpen(false)} + color="gray.400" + _hover={{ color: 'gray.200' }} + /> + + + - {/* 工具选择 - 按分类显示 */} - - - 工具 - {selectedTools.length > 0 && ( - - {selectedTools.length} - - )} -
- } - > -
- - {Object.entries(TOOL_CATEGORIES).map(([category, tools]) => ( - - {category} - - {tools.filter(t => selectedTools.includes(t.id)).length}/{tools.length} - -
- } - > - + + + + + + 模型 + + + + + + 工具 + {selectedTools.length > 0 && ( + + {selectedTools.length} + + )} + + + + + + 统计 + + + + + + {/* 模型选择 */} + + + {AVAILABLE_MODELS.map((model) => ( + setSelectedModel(model.id)} + bg={selectedModel === model.id ? 'rgba(59, 130, 246, 0.2)' : 'rgba(31, 41, 55, 0.5)'} + borderWidth={2} + borderColor={selectedModel === model.id ? 'blue.500' : 'gray.600'} + _hover={{ borderColor: selectedModel === model.id ? 'blue.500' : 'gray.500' }} + transition="all 0.2s" + > + + + + {model.icon} + + + + {model.name} + + + {model.description} + + + {selectedModel === model.id && ( + + )} + + + + ))} + + + + {/* 工具选择 */} + + + {Object.entries(TOOL_CATEGORIES).map(([category, tools]) => ( + + + + {category} + + {tools.filter(t => selectedTools.includes(t.id)).length}/{tools.length} + + + + + + + + {tools.map((tool) => ( + + + {tool.icon} + + {tool.name} + {tool.description} + + + + ))} + + + + + ))} + + + + + + + -
- - -
-
- + {/* 统计信息 */} + + + + + + + 对话数 + + {sessions.length} + + + + + + - {/* 统计信息 */} - - - 统计 -
- } - > -
- - -
-
-

对话数

-

{sessions.length}

-
- -
-
-
+ + + + + 消息数 + + {messages.length} + + + + + + - - -
-
-

消息数

-

{messages.length}

-
- -
-
-
- - - -
-
-

已选工具

-

{selectedTools.length}

-
- -
-
-
-
- - - - - )} - + + + + + 已选工具 + + {selectedTools.length} + + + + + + + + + + + + + + )} + + ); }; @@ -1316,44 +1394,44 @@ 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} - + )} -
-
+ +
); }; @@ -1365,133 +1443,134 @@ const MessageRenderer = ({ message, userAvatar }) => { switch (message.type) { case MessageTypes.USER: return ( -
-
- - -

{message.content}

+ + + + + + {message.content} + {message.files && message.files.length > 0 && ( -
+ {message.files.map((file, idx) => ( - - + + {file.name} - + ))} -
+
)} -
+
} size="sm" - classNames={{ - base: 'bg-gradient-to-br from-blue-500 to-purple-600', - }} + bgGradient="linear(to-br, blue.500, purple.600)" /> -
-
+ + ); case MessageTypes.AGENT_THINKING: return ( -
-
+ + } size="sm" - classNames={{ - base: 'bg-gradient-to-br from-purple-500 to-pink-500', - }} + bgGradient="linear(to-br, purple.500, pink.500)" /> - - - -

{message.content}

-
+ + + + + {message.content} + + -
-
+ + ); case MessageTypes.AGENT_RESPONSE: return ( -
-
+ + } size="sm" - classNames={{ - base: 'bg-gradient-to-br from-purple-500 to-pink-500', - }} + bgGradient="linear(to-br, purple.500, pink.500)" /> - - -

+ + + {message.content} -

+ {message.stepResults && message.stepResults.length > 0 && ( -
+ -
+ )} -
- - + variant="ghost" + icon={} + onClick={() => navigator.clipboard.writeText(message.content)} + color="gray.400" + _hover={{ color: 'gray.200' }} + /> - - + variant="ghost" + icon={} + color="gray.400" + _hover={{ color: 'green.400' }} + /> - - + variant="ghost" + icon={} + color="gray.400" + _hover={{ color: 'red.400' }} + /> - + {new Date(message.timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', })} - -
-
+ +
+ -
-
+ + ); case MessageTypes.ERROR: return ( -
- - -

{message.content}

-
+ + + + {message.content} + -
+ ); default: @@ -1504,62 +1583,52 @@ const MessageRenderer = ({ message, userAvatar }) => { */ 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}

} -
-
- ))} -
-
+ + + + 执行详情 + + {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.heroui-backup b/src/views/AgentChat/index.js.heroui-backup new file mode 100644 index 00000000..468c504c --- /dev/null +++ b/src/views/AgentChat/index.js.heroui-backup @@ -0,0 +1,1565 @@ +// src/views/AgentChat/index.js +// 超炫酷的 AI 投研助手 - Hero UI 深色模式版本 +// 使用 Framer Motion 物理动画引擎 + +import React, { useState, useEffect, useRef } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { + Button, + Card, + Input, + Avatar, + Chip, + Separator, + Spinner, + Tooltip, + Checkbox, + CheckboxGroup, + Kbd, + Accordion, +} from '@heroui/react'; +import { + Tabs, + TabList, + TabPanels, + Tab, + TabPanel, + useToast +} 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'; + +/** + * Framer Motion 动画变体配置 + */ +const animations = { + slideInLeft: { + initial: { x: -320, opacity: 0 }, + animate: { + x: 0, + opacity: 1, + transition: { + type: 'spring', + stiffness: 300, + damping: 30, + }, + }, + exit: { + x: -320, + opacity: 0, + transition: { duration: 0.2 }, + }, + }, + slideInRight: { + initial: { x: 320, opacity: 0 }, + animate: { + x: 0, + opacity: 1, + transition: { + type: 'spring', + stiffness: 300, + damping: 30, + }, + }, + exit: { + x: 320, + opacity: 0, + transition: { duration: 0.2 }, + }, + }, + fadeInUp: { + initial: { opacity: 0, y: 20 }, + animate: { + opacity: 1, + y: 0, + transition: { + type: 'spring', + stiffness: 400, + damping: 25, + }, + }, + }, + staggerItem: { + initial: { opacity: 0, y: 10 }, + animate: { opacity: 1, y: 0 }, + }, + staggerContainer: { + animate: { + transition: { + staggerChildren: 0.05, + }, + }, + }, + pressScale: { + whileTap: { scale: 0.95 }, + whileHover: { scale: 1.05 }, + }, +}; + +/** + * 消息类型 + */ +const MessageTypes = { + USER: 'user', + AGENT_THINKING: 'agent_thinking', + AGENT_PLAN: 'agent_plan', + AGENT_EXECUTING: 'agent_executing', + AGENT_RESPONSE: 'agent_response', + ERROR: 'error', +}; + +/** + * 可用模型配置 + */ +const AVAILABLE_MODELS = [ + { + id: 'kimi-k2-thinking', + name: 'Kimi K2 Thinking', + description: '深度思考模型,适合复杂分析', + icon: , + color: 'secondary', + }, + { + id: 'kimi-k2', + name: 'Kimi K2', + description: '快速响应模型,适合简单查询', + icon: , + color: 'primary', + }, + { + id: 'deepmoney', + name: 'DeepMoney', + description: '金融专业模型', + icon: , + color: 'success', + }, +]; + +/** + * MCP 工具配置(完整列表) + */ +const MCP_TOOLS = [ + // 新闻搜索类 + { + id: 'search_news', + name: '全球新闻搜索', + icon: , + category: '新闻资讯', + description: '搜索全球新闻,支持关键词和日期过滤' + }, + { + id: 'search_china_news', + name: '中国新闻搜索', + icon: , + category: '新闻资讯', + description: 'KNN语义搜索中国新闻' + }, + { + id: 'search_medical_news', + name: '医疗健康新闻', + icon: , + category: '新闻资讯', + description: '医药、医疗设备、生物技术新闻' + }, + + // 概念板块类 + { + id: 'search_concepts', + name: '概念板块搜索', + icon: , + category: '概念板块', + description: '搜索股票概念板块及相关股票' + }, + { + id: 'get_concept_details', + name: '概念详情', + icon: , + category: '概念板块', + description: '获取概念板块详细信息' + }, + { + id: 'get_stock_concepts', + name: '股票概念', + icon: , + category: '概念板块', + description: '查询股票相关概念板块' + }, + { + id: 'get_concept_statistics', + name: '概念统计', + icon: , + category: '概念板块', + description: '涨幅榜、跌幅榜、活跃榜等' + }, + + // 涨停分析类 + { + id: 'search_limit_up_stocks', + name: '涨停股票搜索', + icon: , + category: '涨停分析', + description: '搜索涨停股票,支持多条件筛选' + }, + { + id: 'get_daily_stock_analysis', + name: '涨停日报', + icon: , + category: '涨停分析', + description: '每日涨停股票分析报告' + }, + + // 研报路演类 + { + id: 'search_research_reports', + name: '研报搜索', + icon: , + category: '研报路演', + description: '搜索研究报告,支持语义搜索' + }, + { + id: 'search_roadshows', + name: '路演活动', + icon: , + category: '研报路演', + description: '上市公司路演、投资者交流活动' + }, + + // 股票数据类 + { + id: 'get_stock_basic_info', + name: '股票基本信息', + icon: , + category: '股票数据', + description: '公司名称、行业、主营业务等' + }, + { + id: 'get_stock_financial_index', + name: '财务指标', + icon: , + category: '股票数据', + description: 'EPS、ROE、营收增长率等' + }, + { + id: 'get_stock_trade_data', + name: '交易数据', + icon: , + category: '股票数据', + description: '价格、成交量、涨跌幅等' + }, + { + id: 'get_stock_balance_sheet', + name: '资产负债表', + icon: , + category: '股票数据', + description: '资产、负债、所有者权益' + }, + { + id: 'get_stock_cashflow', + name: '现金流量表', + icon: , + category: '股票数据', + description: '经营、投资、筹资现金流' + }, + { + id: 'search_stocks_by_criteria', + name: '条件选股', + icon: , + category: '股票数据', + description: '按行业、地区、市值筛选' + }, + { + id: 'get_stock_comparison', + name: '股票对比', + icon: , + category: '股票数据', + description: '多只股票财务指标对比' + }, + + // 用户数据类 + { + id: 'get_user_watchlist', + name: '自选股列表', + icon: , + category: '用户数据', + description: '用户关注的股票及行情' + }, + { + id: 'get_user_following_events', + name: '关注事件', + icon: , + category: '用户数据', + description: '用户关注的重大事件' + }, +]; + +// 按类别分组工具 +const TOOL_CATEGORIES = { + '新闻资讯': MCP_TOOLS.filter(t => t.category === '新闻资讯'), + '概念板块': MCP_TOOLS.filter(t => t.category === '概念板块'), + '涨停分析': MCP_TOOLS.filter(t => t.category === '涨停分析'), + '研报路演': MCP_TOOLS.filter(t => t.category === '研报路演'), + '股票数据': MCP_TOOLS.filter(t => t.category === '股票数据'), + '用户数据': MCP_TOOLS.filter(t => t.category === '用户数据'), +}; + +/** + * Hero Agent Chat - 主组件(深色模式) + */ +const AgentChat = () => { + const { user } = useAuth(); + const toast = useToast(); + + // 会话管理 + 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('kimi-k2-thinking'); + const [selectedTools, setSelectedTools] = useState([ + 'search_news', + 'search_china_news', + 'search_concepts', + 'search_limit_up_stocks', + 'search_research_reports', + ]); + 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 页面强制启用深色模式 + document.documentElement.classList.add('dark'); + + return () => { + // 组件卸载时不移除,让其他页面自己控制 + // document.documentElement.classList.remove('dark'); + }; + }, []); + + // ==================== 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]); + + // ==================== 按日期分组会话 ==================== + 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; + + const quickQuestions = [ + { text: '今日涨停板块分析', emoji: '🔥' }, + { text: '新能源概念机会', emoji: '⚡' }, + { text: '半导体行业动态', emoji: '💾' }, + { text: '本周热门研报', emoji: '📊' }, + ]; + + return ( +
+ {/* 左侧栏 - 深色毛玻璃 */} + {isLeftSidebarOpen && ( + +
+
+
+ + 对话历史 +
+
+ + + + + + +
+
+ + } + size="sm" + variant="bordered" + classNames={{ + input: 'text-sm text-gray-100', + inputWrapper: 'border-gray-700 bg-gray-800/50 hover:border-gray-600', + }} + /> +
+ +
+ {/* 按日期分组显示会话 */} + {sessionGroups.today.length > 0 && ( +
+

今天

+
+ {sessionGroups.today.map((session) => ( + 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 && ( + + )} + + + } + classNames={{ + base: 'bg-gradient-to-br from-purple-500 to-pink-500', + }} + /> + + +
+

+ 价小前投研 AI +

+
+ } + classNames={{ + base: 'bg-green-500/20', + content: 'text-green-400', + }} + > + 智能分析 + + + {AVAILABLE_MODELS.find((m) => m.id === selectedModel)?.name} + +
+
+
+ +
+ + + + {!isRightSidebarOpen && ( + + )} +
+
+
+ + {/* 消息列表 */} +
+ + + {messages.map((message, index) => ( + + + + ))} + +
+ +
+ + {/* 快捷问题 */} + + {messages.length <= 2 && !isProcessing && ( + +
+

+ + 快速开始 +

+
+ {quickQuestions.map((question, idx) => ( + + + + ))} +
+
+
+ )} +
+ + {/* 输入栏 - 深色 */} +
+
+ {/* 已上传文件预览 */} + {uploadedFiles.length > 0 && ( +
+ {uploadedFiles.map((file, idx) => ( + removeFile(idx)} + variant="flat" + classNames={{ + base: 'bg-gray-800 border border-gray-700', + content: 'text-gray-300', + }} + > + {file.name} + + ))} +
+ )} + +
+ + + + + + + + + + + + + + + +
+ +
+
+ Enter + 发送 +
+
+ Shift + + + Enter + 换行 +
+
+
+
+
+ + {/* 右侧栏 - 深色配置中心 */} + {isRightSidebarOpen && ( + +
+
+
+ + 配置中心 +
+ + + +
+
+ +
+ + {/* 模型选择 */} + + + 模型 +
+ } + > +
+ {AVAILABLE_MODELS.map((model) => ( + setSelectedModel(model.id)} + className={`transition-all ${ + selectedModel === model.id + ? 'bg-blue-500/20 border-2 border-blue-500' + : 'bg-gray-800/50 border-2 border-gray-700 hover:border-gray-600' + }`} + > + +
+
+ {model.icon} +
+
+

{model.name}

+

{model.description}

+
+ {selectedModel === model.id && ( + + )} +
+
+
+ ))} +
+ + + {/* 工具选择 - 按分类显示 */} + + + 工具 + {selectedTools.length > 0 && ( + + {selectedTools.length} + + )} +
+ } + > +
+ + {Object.entries(TOOL_CATEGORIES).map(([category, tools]) => ( + + {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; + +/** + * 会话卡片组件 + */ +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" + classNames={{ + base: 'bg-gradient-to-br from-blue-500 to-purple-600', + }} + /> +
+
+ ); + + case MessageTypes.AGENT_THINKING: + return ( +
+
+ } + size="sm" + classNames={{ + base: 'bg-gradient-to-br from-purple-500 to-pink-500', + }} + /> + + + +

{message.content}

+
+
+
+
+ ); + + case MessageTypes.AGENT_RESPONSE: + return ( +
+
+ } + size="sm" + classNames={{ + base: 'bg-gradient-to-br from-purple-500 to-pink-500', + }} + /> + + +

+ {message.content} +

+ + {message.stepResults && message.stepResults.length > 0 && ( +
+ +
+ )} + +
+ + + + + + + + + + + {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}

} +
+
+ ))} +
+
+
+ ); +};