// 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';
/**
* 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: 'purple',
},
{
id: 'kimi-k2',
name: 'Kimi K2',
description: '快速响应模型,适合简单查询',
icon: ,
color: 'blue',
},
{
id: 'deepmoney',
name: 'DeepMoney',
description: '金融专业模型',
icon: ,
color: 'green',
},
];
/**
* 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 === '用户数据'),
};
/**
* Agent Chat - 主组件(HeroUI v3 深色主题)
*/
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('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 页面强制启用深色模式
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]);
// ==================== 按日期分组会话 ====================
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 && (
对话历史
}
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}
)}
);
};
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}
)}
))}
);
};