diff --git a/CLAUDE.md b/CLAUDE.md
index 893b0cca..0d161d2f 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -44,7 +44,10 @@
**前端**
- **核心框架**: React 18.3.1
- **类型系统**: TypeScript 5.9.3(渐进式接入中,支持 JS/TS 混合开发)
-- **UI 组件库**: Chakra UI 2.10.9(主要) + Ant Design 5.27.4(表格/表单)
+- **UI 组件库**:
+ - Chakra UI 2.10.9(主要,全局使用)
+ - Ant Design 5.27.4(表格/表单)
+ - **HeroUI 3.0.0-beta**(AgentChat 专用,2025-11-22 升级)
- **状态管理**: Redux Toolkit 2.9.2
- **路由**: React Router v6.30.1 配合 React.lazy() 实现代码分割
- **构建系统**: CRACO 7.1.0 + 激进的 webpack 5 优化
@@ -58,7 +61,8 @@
- **开发工具**: MSW (Mock Service Worker) 用于 API mocking
- **虚拟化**: @tanstack/react-virtual 3.13.12(性能优化)
- **其他**: Draft.js(富文本编辑)、React Markdown、React Quill
-- Use HeroUI v3 documentation from https://v3.heroui.com/llms.txt
+
+**注意**: HeroUI v3 文档参考 https://v3.heroui.com/llms.txt,详细升级说明见 [HEROUI_V3_UPGRADE_GUIDE.md](./HEROUI_V3_UPGRADE_GUIDE.md)
**后端**
- Flask + SQLAlchemy ORM
diff --git a/package.json b/package.json
index 6930f97a..e7a29807 100755
--- a/package.json
+++ b/package.json
@@ -18,7 +18,8 @@
"@fullcalendar/daygrid": "^5.9.0",
"@fullcalendar/interaction": "^5.9.0",
"@fullcalendar/react": "^5.9.0",
- "@heroui/react": "^2.8.5",
+ "@heroui/react": "beta",
+ "@heroui/styles": "beta",
"@reduxjs/toolkit": "^2.9.2",
"@splidejs/react-splide": "^0.7.12",
"@tanstack/react-virtual": "^3.13.12",
diff --git a/src/index.js b/src/index.js
index 3cae50cb..5c4d0027 100755
--- a/src/index.js
+++ b/src/index.js
@@ -2,6 +2,10 @@
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/providers/AppProviders.js b/src/providers/AppProviders.js
index 5f7c4cea..976f53c4 100644
--- a/src/providers/AppProviders.js
+++ b/src/providers/AppProviders.js
@@ -3,7 +3,6 @@
import React from 'react';
import { ChakraProvider } from '@chakra-ui/react';
-import { HeroUIProvider } from '@heroui/react';
import { Provider as ReduxProvider } from 'react-redux';
// Redux Store
@@ -23,11 +22,11 @@ import { NotificationProvider } from '../contexts/NotificationContext';
* Provider 层级顺序 (从外到内):
* 1. ReduxProvider - 状态管理层
* 2. ChakraProvider - UI 框架层(主要)
- * 3. HeroUIProvider - Hero UI 框架层(AgentChat 专用)
- * 4. NotificationProvider - 通知系统
- * 5. AuthProvider - 认证系统
+ * 3. NotificationProvider - 通知系统
+ * 4. AuthProvider - 认证系统
*
* 注意:
+ * - HeroUI v3 不再需要 HeroUIProvider,样式通过 CSS 导入加载 (src/styles/heroui.css)
* - AuthModal 已迁移到 Redux (authModalSlice + useAuthModal Hook)
* - ErrorBoundary 在各 Layout 层实现,不在全局层,以实现精细化错误隔离
* - MainLayout: PageTransitionWrapper 包含 ErrorBoundary (页面错误不影响导航栏)
@@ -49,13 +48,11 @@ export function AppProviders({ children }) {
}
}}
>
-
-
-
- {children}
-
-
-
+
+
+ {children}
+
+
);
diff --git a/src/styles/heroui.css b/src/styles/heroui.css
new file mode 100644
index 00000000..bf9ed064
--- /dev/null
+++ b/src/styles/heroui.css
@@ -0,0 +1,19 @@
+/* HeroUI v3 样式导入 */
+/* 文档: https://v3.heroui.com/docs/quick-start */
+
+/*
+ * 重要: 必须先导入 tailwindcss,再导入 @heroui/styles
+ * HeroUI v3 不再使用 Tailwind 插件,而是通过 CSS 层叠方式加载样式
+ */
+@import "tailwindcss";
+@import "@heroui/styles";
+
+/*
+ * AgentChat 页面的自定义样式
+ * 这些样式仅在使用 HeroUI 组件的页面生效
+ */
+
+/* 确保深色模式正常工作 */
+.dark {
+ color-scheme: dark;
+}
diff --git a/src/views/AgentChat/index_old_chakra.js b/src/views/AgentChat/index_old_chakra.js
deleted file mode 100644
index 39cdefa4..00000000
--- a/src/views/AgentChat/index_old_chakra.js
+++ /dev/null
@@ -1,1017 +0,0 @@
-// 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,
- CardHeader,
- CardBody,
- CardFooter,
- Input,
- Avatar,
- Chip,
- Divider,
- Spinner,
- Tooltip,
- Badge,
- Checkbox,
- CheckboxGroup,
- Tabs,
- Tab,
- ScrollShadow,
- Kbd,
- Accordion,
- AccordionItem,
-} from '@heroui/react';
-import { useAuth } from '@contexts/AuthContext';
-import { logger } from '@utils/logger';
-import axios from 'axios';
-import { useToast } from '@chakra-ui/react';
-
-// 图标 - 使用 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,
-} 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',
- },
-];
-
-/**
- * 可用工具配置
- */
-const AVAILABLE_TOOLS = [
- { id: 'search_news', name: '新闻搜索', icon: },
- { id: 'search_limit_up', name: '涨停分析', icon: },
- { id: 'search_concept', name: '概念板块', icon: },
- { id: 'search_research_report', name: '研报搜索', icon: },
- { id: 'search_roadshow', name: '路演信息', icon: },
-];
-
-/**
- * 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_limit_up',
- 'search_concept',
- ]);
- const [isLeftSidebarOpen, setIsLeftSidebarOpen] = useState(true);
- const [isRightSidebarOpen, setIsRightSidebarOpen] = useState(true);
-
- // Refs
- const messagesEndRef = useRef(null);
- const inputRef = useRef(null);
-
- // ==================== 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(),
- };
-
- addMessage(userMessage);
- const userInput = inputValue;
- setInputValue('');
- 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,
- });
-
- 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,
- isClosable: true,
- });
- } finally {
- setIsProcessing(false);
- inputRef.current?.focus();
- }
- };
-
- const addMessage = (message) => {
- setMessages((prev) => [...prev, { ...message, id: Date.now() + Math.random() }]);
- };
-
- const scrollToBottom = () => {
- messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
- };
-
- useEffect(() => {
- scrollToBottom();
- }, [messages]);
-
- const handleKeyPress = (e) => {
- if (e.key === 'Enter' && !e.shiftKey) {
- e.preventDefault();
- handleSendMessage();
- }
- };
-
- useEffect(() => {
- if (user) {
- loadSessions();
- createNewSession();
- }
- }, [user]);
-
- const filteredSessions = sessions.filter(
- (session) =>
- !searchQuery || session.last_message?.toLowerCase().includes(searchQuery.toLowerCase())
- );
-
- const quickQuestions = [
- { text: '全面分析贵州茅台', emoji: '📊' },
- { text: '今日涨停股票分析', emoji: '🔥' },
- { text: '新能源概念机会', emoji: '⚡' },
- { text: '半导体行业动态', emoji: '💾' },
- ];
-
- return (
-
- {/* 左侧栏 */}
- {isLeftSidebarOpen && (
-
-
-
-
-
-
对话历史
-
-
-
-
-
}
- onPress={createNewSession}
- className="w-full mb-3"
- >
- 新建对话
-
-
-
setSearchQuery(e.target.value)}
- startContent={
}
- size="sm"
- variant="bordered"
- />
-
-
-
- {isLoadingSessions ? (
-
-
-
- ) : filteredSessions.length === 0 ? (
-
-
-
{searchQuery ? '没有找到匹配的对话' : '暂无对话记录'}
-
- ) : (
-
- {filteredSessions.map((session) => (
-
switchSession(session.session_id)}
- className={`transition-all ${
- currentSessionId === session.session_id
- ? 'bg-primary-50 dark:bg-primary-900/20 border-primary-500'
- : ''
- }`}
- >
-
-
-
-
- {session.last_message || '新对话'}
-
-
-
-
- {new Date(session.last_timestamp).toLocaleDateString('zh-CN', {
- month: 'numeric',
- day: 'numeric',
- hour: 'numeric',
- minute: 'numeric',
- })}
-
-
- {session.message_count}
-
-
-
-
-
-
-
- ))}
-
- )}
-
-
-
-
-
-
-
{user?.nickname || '未登录'}
-
{user?.id || 'anonymous'}
-
-
-
-
- )}
-
- {/* 中间主聊天区域 */}
-
-
-
-
- {!isLeftSidebarOpen && (
-
- )}
-
-
- }
- classNames={{
- base: 'bg-gradient-to-br from-purple-500 to-pink-500',
- }}
- />
-
-
-
-
- 价小前投研 AI
-
-
- }
- >
- 智能分析
-
-
- {AVAILABLE_MODELS.find((m) => m.id === selectedModel)?.name}
-
-
-
-
-
-
-
-
-
- {!isRightSidebarOpen && (
-
- )}
-
-
-
-
-
-
-
- {messages.map((message, index) => (
-
-
-
- ))}
-
-
-
-
-
-
- {messages.length <= 2 && !isProcessing && (
-
-
-
-
- 快速开始
-
-
- {quickQuestions.map((question, idx) => (
-
-
-
- ))}
-
-
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
- Enter
- 发送
-
-
- Shift
- +
- Enter
- 换行
-
-
-
-
-
-
- {/* 右侧栏 */}
- {isRightSidebarOpen && (
-
-
-
-
-
-
配置中心
-
-
-
-
-
-
-
-
-
- {AVAILABLE_MODELS.map((model) => (
-
setSelectedModel(model.id)}
- className={`hover:scale-102 transition-transform ${
- selectedModel === model.id
- ? 'border-2 border-' + model.color + '-500 bg-' + model.color + '-50'
- : 'border-2 border-transparent'
- }`}
- >
-
-
-
-
{model.icon}
-
-
{model.name}
-
{model.description}
-
-
- {selectedModel === model.id && (
-
- )}
-
-
-
- ))}
-
-
-
-
-
-
选择 AI 可以使用的工具
-
-
- {AVAILABLE_TOOLS.map((tool) => (
-
-
- {tool.icon}
- {tool.name}
-
-
- ))}
-
-
-
-
-
-
-
-
-
- 历史会话
-
- {sessions.length}
-
-
-
-
- 当前消息
-
- {messages.length}
-
-
-
-
- 启用工具
-
- {selectedTools.length}
-
-
-
-
-
-
-
-
- )}
-
- );
-};
-
-export default AgentChat;
-
-/**
- * 消息渲染器
- */
-const MessageRenderer = ({ message, userAvatar }) => {
- switch (message.type) {
- case MessageTypes.USER:
- return (
-
-
-
-
- {message.content}
-
-
-
} size="sm" />
-
-
- );
-
- case MessageTypes.AGENT_THINKING:
- return (
-
-
-
}
- classNames={{ base: 'bg-gradient-to-br from-purple-500 to-pink-500' }}
- size="sm"
- />
-
-
-
-
-
- {message.content}
-
-
-
-
-
-
- );
-
- case MessageTypes.AGENT_RESPONSE:
- return (
-
-
-
}
- classNames={{ base: 'bg-gradient-to-br from-blue-500 to-purple-500' }}
- size="sm"
- />
-
-
-
- {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.stepResults && message.stepResults.length > 0 && (
-
- )}
-
-
-
- );
-
- case MessageTypes.ERROR:
- return (
-
-
-
}
- classNames={{ base: 'bg-danger-500' }}
- size="sm"
- />
-
-
- {message.content}
-
-
-
-
- );
-
- default:
- return null;
- }
-};
-
-/**
- * 步骤结果面板
- */
-const StepResultsPanel = ({ stepResults }) => {
- return (
-
-
-
- 执行详情 ({stepResults.length} 个步骤)
-
- }
- >
-
- {stepResults.map((result, idx) => (
-
-
-
-
-
- 步骤 {idx + 1}: {result.tool}
-
-
- {result.status}
-
-
-
{result.execution_time?.toFixed(2)}s
-
- {result.error && ⚠️ {result.error}
}
-
-
- ))}
-
-
-
- );
-};
diff --git a/src/views/AgentChat/index_v2.js b/src/views/AgentChat/index_v2.js
deleted file mode 100644
index 050c4944..00000000
--- a/src/views/AgentChat/index_v2.js
+++ /dev/null
@@ -1,1569 +0,0 @@
-// 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,
- CardHeader,
- CardBody,
- CardFooter,
- Input,
- Avatar,
- Chip,
- Divider,
- Spinner,
- Tooltip,
- Badge,
- Checkbox,
- CheckboxGroup,
- Tabs,
- Tab,
- ScrollShadow,
- Kbd,
- Accordion,
- AccordionItem,
-} from '@heroui/react';
-import { useAuth } from '@contexts/AuthContext';
-import { logger } from '@utils/logger';
-import axios from 'axios';
-import { useToast } from '@chakra-ui/react';
-
-// 图标 - 使用 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 && (
-
- )}
-
-
-
- ))}
-
-
-
- {/* 工具选择 - 按分类显示 */}
-
-
- 工具
-
-
- }
- >
-
-
- {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}
}
-
-
- ))}
-
-
-
- );
-};
diff --git a/tailwind.config.js b/tailwind.config.js
index bd5e5b5a..2a05091a 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,11 +1,12 @@
/** @type {import('tailwindcss').Config} */
-const { heroui } = require("@heroui/react");
module.exports = {
// 只扫描 AgentChat 页面(唯一使用 Hero UI 的地方)
+ // 使用精确路径,避免误匹配 node_modules
content: [
- "./src/views/AgentChat/**/*.{js,jsx}",
+ "./src/views/AgentChat/index.js",
"./src/providers/AppProviders.js", // HeroUIProvider
+ // HeroUI v3 不再需要扫描 node_modules,样式通过 CSS 导入加载
],
darkMode: "class",
@@ -14,5 +15,6 @@ module.exports = {
extend: {},
},
- plugins: [heroui()],
+ // HeroUI v3 不再需要 plugins,样式通过 @import "@heroui/styles" 加载
+ plugins: [],
}