// src/views/AgentChat/index_v4.js
// Agent聊天页面 V4 - 黑金毛玻璃设计,带模型选择和工具选择
import React, { useState, useEffect, useRef } from 'react';
import { Global, css } from '@emotion/react';
import {
Box,
Flex,
VStack,
HStack,
Text,
Input,
IconButton,
Button,
Avatar,
Heading,
Divider,
Spinner,
Badge,
useToast,
Progress,
Fade,
Collapse,
InputGroup,
InputLeftElement,
Menu,
MenuButton,
MenuList,
MenuItem,
Tooltip,
Select,
Checkbox,
CheckboxGroup,
Stack,
Accordion,
AccordionItem,
AccordionButton,
AccordionPanel,
AccordionIcon,
useDisclosure,
} from '@chakra-ui/react';
import {
FiSend,
FiSearch,
FiPlus,
FiMessageSquare,
FiTrash2,
FiMoreVertical,
FiRefreshCw,
FiDownload,
FiCpu,
FiUser,
FiZap,
FiClock,
FiSettings,
FiCheckCircle,
FiChevronRight,
FiTool,
} from 'react-icons/fi';
import { useAuth } from '@contexts/AuthContext';
import { PlanCard } from '@components/ChatBot/PlanCard';
import { StepResultCard } from '@components/ChatBot/StepResultCard';
import { MarkdownWithCharts } from '@components/ChatBot/MarkdownWithCharts';
import { logger } from '@utils/logger';
import axios from 'axios';
import HomeNavbar from '@components/Navbars/HomeNavbar';
/**
* Agent消息类型
*/
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',
name: 'Kimi K2',
description: '快速响应,适合日常对话',
icon: '🚀',
provider: 'Moonshot',
},
{
id: 'kimi-k2-thinking',
name: 'Kimi K2 Thinking',
description: '深度思考,提供详细推理过程',
icon: '🧠',
provider: 'Moonshot',
recommended: true,
},
{
id: 'glm-4.6',
name: 'GLM 4.6',
description: '智谱AI最新模型,性能强大',
icon: '⚡',
provider: 'ZhipuAI',
},
{
id: 'deepmoney',
name: 'DeepMoney',
description: '金融专业模型,擅长财经分析',
icon: '💰',
provider: 'Custom',
},
{
id: 'gemini-3',
name: 'Gemini 3',
description: 'Google最新多模态模型',
icon: '✨',
provider: 'Google',
},
];
/**
* MCP工具分类配置
*/
const MCP_TOOL_CATEGORIES = [
{
name: '新闻搜索',
icon: '📰',
tools: [
{ id: 'search_news', name: '全球新闻搜索', description: '搜索国际新闻、行业动态' },
{ id: 'search_china_news', name: '中国新闻搜索', description: 'KNN语义搜索中国新闻' },
{ id: 'search_medical_news', name: '医疗新闻搜索', description: '医药、医疗设备、生物技术' },
],
},
{
name: '股票分析',
icon: '📈',
tools: [
{ id: 'search_limit_up_stocks', name: '涨停股票搜索', description: '搜索涨停股票及原因分析' },
{ id: 'get_stock_analysis', name: '个股分析', description: '获取股票深度分析报告' },
{ id: 'get_stock_concepts', name: '股票概念查询', description: '查询股票相关概念板块' },
],
},
{
name: '概念板块',
icon: '🏢',
tools: [
{ id: 'search_concepts', name: '概念搜索', description: '搜索股票概念板块' },
{ id: 'get_concept_details', name: '概念详情', description: '获取概念板块详细信息' },
{ id: 'get_concept_statistics', name: '概念统计', description: '涨幅榜、活跃榜、连涨榜' },
],
},
{
name: '公司信息',
icon: '🏭',
tools: [
{ id: 'search_roadshows', name: '路演搜索', description: '搜索上市公司路演活动' },
{ id: 'get_company_info', name: '公司信息', description: '获取公司基本面信息' },
],
},
{
name: '数据分析',
icon: '📊',
tools: [
{ id: 'query_database', name: '数据库查询', description: 'SQL查询金融数据' },
{ id: 'get_market_overview', name: '市场概况', description: '获取市场整体行情' },
],
},
];
/**
* Agent聊天页面 V4 - 黑金毛玻璃设计
*/
const AgentChatV4 = () => {
const { user } = useAuth();
const toast = useToast();
// 确保组件总是返回有效的 React 元素
if (!user) {
return (
正在加载...
);
}
// 会话相关状态
const [sessions, setSessions] = useState([]);
const [currentSessionId, setCurrentSessionId] = useState(null);
const [isLoadingSessions, setIsLoadingSessions] = useState(true);
// 消息相关状态
const [messages, setMessages] = useState([]);
const [inputValue, setInputValue] = useState('');
const [isProcessing, setIsProcessing] = useState(false);
const [currentProgress, setCurrentProgress] = useState(0);
// 模型和工具配置状态
const [selectedModel, setSelectedModel] = useState('kimi-k2-thinking');
const [selectedTools, setSelectedTools] = useState(() => {
// 默认全选所有工具
const allToolIds = MCP_TOOL_CATEGORIES.flatMap(cat => cat.tools.map(t => t.id));
return allToolIds;
});
// UI 状态
const [searchQuery, setSearchQuery] = useState('');
// 检测是否为移动设备(屏幕宽度小于 768px)
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
// 根据设备类型设置侧边栏默认状态(移动端默认收起)
const { isOpen: isSidebarOpen, onToggle: toggleSidebar } = useDisclosure({
defaultIsOpen: !isMobile
});
const { isOpen: isRightPanelOpen, onToggle: toggleRightPanel } = useDisclosure({
defaultIsOpen: !isMobile
});
// Refs
const messagesEndRef = useRef(null);
const inputRef = useRef(null);
// 毛玻璃深灰金配色主题(类似编程工具的深色主题)
const glassBg = 'rgba(30, 35, 40, 0.85)'; // 深灰色毛玻璃
const glassHoverBg = 'rgba(40, 45, 50, 0.9)';
const goldAccent = '#FFD700';
const goldGradient = 'linear-gradient(135deg, #FFD700 0%, #FFA500 100%)';
const darkBg = '#1a1d23'; // VS Code 风格的深灰背景
const borderGold = 'rgba(255, 215, 0, 0.3)';
const textGold = '#FFD700';
const textWhite = '#E8E8E8'; // 柔和的白色
const textGray = '#9BA1A6'; // 柔和的灰色
const cardBg = 'rgba(40, 45, 50, 0.6)'; // 卡片背景(深灰毛玻璃)
// ==================== 会话管理函数 ====================
const loadSessions = async () => {
if (!user?.id) return;
setIsLoadingSessions(true);
try {
const response = await axios.get('/mcp/agent/sessions', {
params: { user_id: String(user.id), limit: 50 },
});
if (response.data.success) {
setSessions(response.data.data);
logger.info('会话列表加载成功', response.data.data);
}
} catch (error) {
logger.error('加载会话列表失败', error);
toast({
title: '加载失败',
description: '无法加载会话列表',
status: 'error',
duration: 3000,
});
} finally {
setIsLoadingSessions(false);
}
};
const loadSessionHistory = async (sessionId) => {
if (!sessionId) return;
try {
const response = await axios.get(`/mcp/agent/history/${sessionId}`, {
params: { limit: 100 },
});
if (response.data.success) {
const history = response.data.data;
const formattedMessages = history.map((msg, idx) => ({
id: `${sessionId}-${idx}`,
type: msg.message_type === 'user' ? MessageTypes.USER : MessageTypes.AGENT_RESPONSE,
content: msg.message,
plan: msg.plan ? JSON.parse(msg.plan) : null,
stepResults: msg.steps ? JSON.parse(msg.steps) : null,
timestamp: msg.timestamp,
}));
setMessages(formattedMessages);
logger.info('会话历史加载成功', formattedMessages);
}
} catch (error) {
logger.error('加载会话历史失败', error);
toast({
title: '加载失败',
description: '无法加载会话历史',
status: 'error',
duration: 3000,
});
}
};
const createNewSession = () => {
setCurrentSessionId(null);
setMessages([
{
id: Date.now(),
type: MessageTypes.AGENT_RESPONSE,
content: `你好${user?.nickname || ''}!我是价小前,北京价值前沿科技公司的AI投研助手。\n\n我会通过多步骤分析来帮助你深入了解金融市场。\n\n你可以问我:\n• 全面分析某只股票\n• 某个行业的投资机会\n• 今日市场热点\n• 某个概念板块的表现`,
timestamp: new Date().toISOString(),
},
]);
};
const switchSession = (sessionId) => {
setCurrentSessionId(sessionId);
loadSessionHistory(sessionId);
};
const deleteSession = async (sessionId) => {
toast({
title: '删除会话',
description: '此功能尚未实现',
status: 'info',
duration: 2000,
});
};
// ==================== 消息处理函数 ====================
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
// 监听窗口大小变化,动态更新移动端状态
useEffect(() => {
const handleResize = () => {
const mobile = window.innerWidth < 768;
setIsMobile(mobile);
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
// 在 AgentChat 页面隐藏 Bytedesk 客服插件(避免遮挡页面)
useEffect(() => {
// 隐藏 Bytedesk - 更安全的方式
const hideBytedeskElements = () => {
try {
// 查找所有 Bytedesk 相关元素
const bytedeskElements = document.querySelectorAll(
'[class*="bytedesk"], [id*="bytedesk"], [class*="BytedeskWeb"], .bytedesk-widget'
);
// 保存原始 display 值
const originalDisplays = new Map();
bytedeskElements.forEach(el => {
if (el && el.style) {
originalDisplays.set(el, el.style.display);
el.style.display = 'none';
}
});
// 返回清理函数
return () => {
bytedeskElements.forEach(el => {
if (el && el.style) {
const originalDisplay = originalDisplays.get(el);
if (originalDisplay !== undefined) {
el.style.display = originalDisplay;
} else {
el.style.display = '';
}
}
});
};
} catch (error) {
console.warn('Failed to hide Bytedesk elements:', error);
return () => {}; // 返回空清理函数
}
};
const cleanup = hideBytedeskElements();
// 组件卸载时恢复显示
return cleanup;
}, []);
const addMessage = (message) => {
setMessages((prev) => [...prev, { ...message, id: Date.now() }]);
};
const handleSendMessage = async () => {
if (!inputValue.trim() || isProcessing) return;
const hasAccess = user?.subscription_type === 'max';
if (!hasAccess) {
logger.warn('AgentChat', '权限检查失败', {
userId: user?.id,
subscription_type: user?.subscription_type,
});
toast({
title: '订阅升级',
description: '「价小前投研」功能需要 Max 订阅。请前往设置页面升级您的订阅。',
status: 'warning',
duration: 5000,
isClosable: true,
});
return;
}
const userMessage = {
type: MessageTypes.USER,
content: inputValue,
timestamp: new Date().toISOString(),
};
addMessage(userMessage);
const userInput = inputValue;
setInputValue('');
setIsProcessing(true);
setCurrentProgress(0);
let currentPlan = null;
let stepResults = [];
let executingMessageId = null;
try {
addMessage({
type: MessageTypes.AGENT_THINKING,
content: '正在分析你的问题...',
timestamp: new Date().toISOString(),
});
setCurrentProgress(10);
const requestBody = {
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 ? String(user.id) : 'anonymous',
user_nickname: user?.nickname || user?.username || '匿名用户',
user_avatar: user?.avatar || '',
subscription_type: user?.subscription_type || 'free',
session_id: currentSessionId,
model: selectedModel, // 传递选中的模型
tools: selectedTools, // 传递选中的工具
};
const response = await fetch('/mcp/agent/chat/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let thinkingMessageId = null;
let thinkingContent = '';
let summaryMessageId = null;
let summaryContent = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
let currentEvent = null;
for (const line of lines) {
if (!line.trim() || line.startsWith(':')) continue;
if (line.startsWith('event:')) {
currentEvent = line.substring(6).trim();
continue;
}
if (line.startsWith('data:')) {
try {
const data = JSON.parse(line.substring(5).trim());
if (currentEvent === 'thinking') {
if (!thinkingMessageId) {
thinkingMessageId = Date.now();
thinkingContent = '';
setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_THINKING));
addMessage({
id: thinkingMessageId,
type: MessageTypes.AGENT_THINKING,
content: '',
timestamp: new Date().toISOString(),
});
}
thinkingContent += data.content;
setMessages((prev) =>
prev.map((m) =>
m.id === thinkingMessageId
? { ...m, content: thinkingContent }
: m
)
);
} else if (currentEvent === 'plan') {
currentPlan = data;
thinkingMessageId = null;
thinkingContent = '';
setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_THINKING));
addMessage({
type: MessageTypes.AGENT_PLAN,
content: '已制定执行计划',
plan: data,
timestamp: new Date().toISOString(),
});
setCurrentProgress(30);
} else if (currentEvent === 'step_complete') {
const stepResult = {
step_index: data.step_index,
tool: data.tool,
status: data.status,
result: data.result,
error: data.error,
execution_time: data.execution_time,
};
stepResults.push(stepResult);
setMessages((prev) =>
prev.map((m) =>
m.id === executingMessageId
? { ...m, stepResults: [...stepResults] }
: m
)
);
const progress = 40 + (stepResults.length / (currentPlan?.steps?.length || 5)) * 40;
setCurrentProgress(Math.min(progress, 80));
} else if (currentEvent === 'summary_chunk') {
if (!summaryMessageId) {
summaryMessageId = Date.now();
summaryContent = '';
setMessages((prev) =>
prev.filter((m) => m.type !== MessageTypes.AGENT_THINKING && m.type !== MessageTypes.AGENT_EXECUTING)
);
addMessage({
id: summaryMessageId,
type: MessageTypes.AGENT_RESPONSE,
content: '',
plan: currentPlan,
stepResults: stepResults,
isStreaming: true,
timestamp: new Date().toISOString(),
});
setCurrentProgress(85);
}
summaryContent += data.content;
setMessages((prev) =>
prev.map((m) =>
m.id === summaryMessageId
? { ...m, content: summaryContent }
: m
)
);
} else if (currentEvent === 'summary') {
if (summaryMessageId) {
setMessages((prev) =>
prev.map((m) =>
m.id === summaryMessageId
? {
...m,
content: data.content || summaryContent,
metadata: data.metadata,
isStreaming: false,
}
: m
)
);
} else {
setMessages((prev) =>
prev.filter((m) => m.type !== MessageTypes.AGENT_THINKING && m.type !== MessageTypes.AGENT_EXECUTING)
);
addMessage({
type: MessageTypes.AGENT_RESPONSE,
content: data.content,
plan: currentPlan,
stepResults: stepResults,
metadata: data.metadata,
isStreaming: false,
timestamp: new Date().toISOString(),
});
}
setCurrentProgress(100);
} else if (currentEvent === 'status') {
if (data.stage === 'planning') {
setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_THINKING));
addMessage({
type: MessageTypes.AGENT_THINKING,
content: data.message,
timestamp: new Date().toISOString(),
});
setCurrentProgress(10);
} else if (data.stage === 'executing') {
const msgId = Date.now();
executingMessageId = msgId;
addMessage({
id: msgId,
type: MessageTypes.AGENT_EXECUTING,
content: data.message,
plan: currentPlan,
stepResults: [],
timestamp: new Date().toISOString(),
});
setCurrentProgress(40);
} else if (data.stage === 'summarizing') {
setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_EXECUTING));
addMessage({
type: MessageTypes.AGENT_THINKING,
content: data.message,
timestamp: new Date().toISOString(),
});
setCurrentProgress(80);
}
}
} catch (e) {
logger.error('解析 SSE 数据失败', e);
}
}
}
}
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?.detail || error.message || '处理失败';
addMessage({
type: MessageTypes.ERROR,
content: `处理失败:${errorMessage}`,
timestamp: new Date().toISOString(),
});
toast({
title: '处理失败',
description: errorMessage,
status: 'error',
duration: 5000,
isClosable: true,
});
} finally {
setIsProcessing(false);
setCurrentProgress(0);
inputRef.current?.focus();
}
};
const handleKeyPress = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSendMessage();
}
};
const handleClearChat = () => {
createNewSession();
};
const handleExportChat = () => {
const chatText = messages
.filter((m) => m.type === MessageTypes.USER || m.type === MessageTypes.AGENT_RESPONSE)
.map((msg) => `[${msg.type === MessageTypes.USER ? '用户' : '价小前'}] ${msg.content}`)
.join('\n\n');
const blob = new Blob([chatText], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `chat_${new Date().toISOString().slice(0, 10)}.txt`;
a.click();
URL.revokeObjectURL(url);
};
// ==================== 工具选择处理 ====================
const handleToolToggle = (toolId, isChecked) => {
if (isChecked) {
setSelectedTools((prev) => [...prev, toolId]);
} else {
setSelectedTools((prev) => prev.filter((id) => id !== toolId));
}
};
const handleCategoryToggle = (categoryTools, isAllSelected) => {
const toolIds = categoryTools.map((t) => t.id);
if (isAllSelected) {
// 全部取消选中
setSelectedTools((prev) => prev.filter((id) => !toolIds.includes(id)));
} else {
// 全部选中
setSelectedTools((prev) => {
const newTools = [...prev];
toolIds.forEach((id) => {
if (!newTools.includes(id)) {
newTools.push(id);
}
});
return newTools;
});
}
};
// ==================== 初始化 ====================
useEffect(() => {
if (user) {
loadSessions();
createNewSession();
}
}, [user]);
// ==================== 渲染 ====================
const quickQuestions = [
'全面分析贵州茅台这只股票',
'今日涨停股票有哪些亮点',
'新能源概念板块的投资机会',
'半导体行业最新动态',
];
const filteredSessions = sessions.filter(
(session) =>
!searchQuery ||
session.last_message?.toLowerCase().includes(searchQuery.toLowerCase())
);
return (
{/* 顶部导航栏 */}
{/* 全局样式:确保页面正确显示 */}
{/* 主内容区域 */}
{/* 左侧会话列表 */}
{/* 侧边栏头部 */}
}
bg={goldGradient}
color={darkBg}
w="100%"
onClick={createNewSession}
size="sm"
_hover={{
transform: 'translateY(-2px)',
boxShadow: '0 4px 20px rgba(255, 215, 0, 0.4)',
}}
transition="all 0.3s"
fontWeight="bold"
>
新建对话
setSearchQuery(e.target.value)}
bg="rgba(255, 255, 255, 0.05)"
border="1px solid"
borderColor={borderGold}
color={textWhite}
_placeholder={{ color: textGray }}
_hover={{ borderColor: goldAccent }}
_focus={{ borderColor: goldAccent, boxShadow: `0 0 0 1px ${goldAccent}` }}
/>
{/* 会话列表 */}
{isLoadingSessions ? (
) : filteredSessions.length === 0 ? (
{searchQuery ? '没有找到匹配的对话' : '暂无对话记录'}
) : (
filteredSessions.map((session) => (
switchSession(session.session_id)}
transition="all 0.2s"
>
{session.last_message || '新对话'}
{new Date(session.last_timestamp).toLocaleDateString('zh-CN', {
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
})}
{session.message_count} 条
))
)}
{/* 用户信息 */}
{user?.nickname || '未登录'}
MAX 订阅
{/* 主聊天区域 */}
{/* 聊天头部 */}
价小前投研
AI 深度分析
{AVAILABLE_MODELS.find(m => m.id === selectedModel)?.name || '智能模型'}
}
size="sm"
variant="ghost"
color={goldAccent}
_hover={{ bg: 'rgba(255, 215, 0, 0.1)' }}
aria-label="历史对话"
onClick={toggleSidebar}
/>
}
size="sm"
variant="ghost"
color={textGray}
_hover={{ color: goldAccent, bg: 'rgba(255, 215, 0, 0.1)' }}
aria-label="清空对话"
onClick={handleClearChat}
/>
}
size="sm"
variant="ghost"
color={textGray}
_hover={{ color: goldAccent, bg: 'rgba(255, 215, 0, 0.1)' }}
aria-label="导出对话"
onClick={handleExportChat}
display={{ base: "none", sm: "inline-flex" }}
/>
}
size="sm"
variant="ghost"
color={goldAccent}
_hover={{ bg: 'rgba(255, 215, 0, 0.1)' }}
aria-label="设置"
onClick={toggleRightPanel}
/>
{/* 进度条 */}
{isProcessing && (
{/* 消息列表 */}
{messages.map((message) => (
))}
{/* 快捷问题 */}
{messages.length <= 2 && !isProcessing && (
💡 试试这些问题:
{quickQuestions.map((question, idx) => (
))}
)}
{/* 输入框 */}
setInputValue(e.target.value)}
onKeyPress={handleKeyPress}
placeholder={isMobile ? "输入问题..." : "输入你的问题,我会进行深度分析..."}
bg="rgba(255, 255, 255, 0.05)"
border="1px solid"
borderColor={borderGold}
color={textWhite}
_placeholder={{ color: textGray }}
_focus={{ borderColor: goldAccent, boxShadow: `0 0 0 1px ${goldAccent}` }}
mr={2}
disabled={isProcessing}
size={{ base: "md", md: "lg" }}
/>
{/* 右侧配置面板 */}
{/* 模型选择 */}
选择模型
{AVAILABLE_MODELS.map((model) => (
setSelectedModel(model.id)}
transition="all 0.2s"
_hover={{
bg: 'rgba(255, 215, 0, 0.1)',
borderColor: goldAccent,
transform: 'translateX(4px)',
}}
position="relative"
>
{model.recommended && (
推荐
)}
{model.icon}
{model.name}
{model.description}
{selectedModel === model.id && (
)}
))}
{/* 工具选择 */}
MCP 工具
{selectedTools.length} 个已选
{MCP_TOOL_CATEGORIES.map((category, catIdx) => {
const categoryToolIds = category.tools.map((t) => t.id);
const selectedInCategory = categoryToolIds.filter((id) => selectedTools.includes(id));
const isAllSelected = selectedInCategory.length === categoryToolIds.length;
const isSomeSelected = selectedInCategory.length > 0 && !isAllSelected;
return (
{category.icon}
{category.name}
{selectedInCategory.length}/{category.tools.length}
{/* 全选按钮 */}
{category.tools.map((tool) => (
handleToolToggle(tool.id, e.target.checked)}
colorScheme="yellow"
size="sm"
sx={{
'.chakra-checkbox__control': {
borderColor: borderGold,
bg: 'rgba(255, 255, 255, 0.05)',
_checked: {
bg: goldGradient,
borderColor: goldAccent,
},
},
}}
>
{tool.name}
{tool.description}
))}
);
})}
);
};
/**
* 消息渲染器(深灰毛玻璃风格)
*/
const MessageRenderer = ({ message, userAvatar }) => {
const glassBg = 'rgba(30, 35, 40, 0.85)'; // 深灰色毛玻璃
const cardBg = 'rgba(40, 45, 50, 0.6)'; // 卡片背景
const goldAccent = '#FFD700';
const goldGradient = 'linear-gradient(135deg, #FFD700 0%, #FFA500 100%)';
const darkBg = '#1a1d23';
const borderGold = 'rgba(255, 215, 0, 0.3)';
const textWhite = '#E8E8E8'; // 柔和的白色
const textGray = '#9BA1A6'; // 柔和的灰色
switch (message.type) {
case MessageTypes.USER:
return (
{message.content}
}
border="2px solid"
borderColor={goldAccent}
/>
);
case MessageTypes.AGENT_THINKING:
return (
{message.content}
);
case MessageTypes.AGENT_PLAN:
return (
);
case MessageTypes.AGENT_EXECUTING:
return (
{message.stepResults?.map((result, idx) => (
))}
);
case MessageTypes.AGENT_RESPONSE:
return (
{/* 最终总结 */}
{message.isStreaming ? (
{message.content}
) : (
)}
{message.metadata && (
总步骤: {message.metadata.total_steps}
✓ {message.metadata.successful_steps}
{message.metadata.failed_steps > 0 && (
✗ {message.metadata.failed_steps}
)}
耗时: {message.metadata.total_execution_time?.toFixed(1)}s
)}
{/* 执行详情(可选) */}
{message.plan && message.stepResults && message.stepResults.length > 0 && (
📊 执行详情(点击展开查看)
{message.stepResults.map((result, idx) => (
))}
)}
);
case MessageTypes.ERROR:
return (
{message.content}
);
default:
return null;
}
};
export default AgentChatV4;