diff --git a/mcp_server.py b/mcp_server.py index 917a8f0c..8c2838ce 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -2377,7 +2377,7 @@ MEETING_ROLES = { "name": "巴菲特", "nickname": "唱多者", "role_type": "bull", - "avatar": "/avatars/buffett.png", + "avatar": "/images/agent/巴菲特.png", "model": "kimi-k2-thinking", "color": "#10B981", "description": "主观多头,善于分析事件的潜在利好和长期价值", @@ -2403,7 +2403,7 @@ MEETING_ROLES = { "name": "大空头", "nickname": "大空头", "role_type": "bear", - "avatar": "/avatars/big_short.png", + "avatar": "/images/agent/大空头.png", "model": "kimi-k2-thinking", "color": "#EF4444", "description": "善于分析事件和财报中的风险因素", @@ -2429,7 +2429,7 @@ MEETING_ROLES = { "name": "量化分析员", "nickname": "西蒙斯", "role_type": "quant", - "avatar": "/avatars/simons.png", + "avatar": "/images/agent/simons.png", "model": "deepseek", "color": "#3B82F6", "description": "中性立场,使用量化工具分析技术指标", @@ -2454,7 +2454,7 @@ MEETING_ROLES = { "name": "韭菜", "nickname": "牢大", "role_type": "retail", - "avatar": "/avatars/leek.png", + "avatar": "/images/agent/牢大.png", "model": "deepmoney", "color": "#F59E0B", "description": "贪婪又讨厌亏损,热爱追涨杀跌", @@ -2474,7 +2474,7 @@ MEETING_ROLES = { "name": "基金经理", "nickname": "决策者", "role_type": "manager", - "avatar": "/avatars/fund_manager.png", + "avatar": "/images/agent/基金经理.png", "model": "deepseek", "color": "#8B5CF6", "description": "综合分析做出最终决策", @@ -2650,7 +2650,7 @@ async def stream_role_response( messages=messages, stream=True, temperature=0.7, - max_tokens=500, + max_tokens=2000, # 增加 token 限制以避免输出被截断 ) full_content = "" diff --git a/public/images/agent/基金经理.png b/public/images/agent/基金经理.png index a5c69801..afc6c374 100644 Binary files a/public/images/agent/基金经理.png and b/public/images/agent/基金经理.png differ diff --git a/src/views/AgentChat/components/ChatArea/MessageRenderer.js b/src/views/AgentChat/components/ChatArea/MessageRenderer.js index aae36850..981107b6 100644 --- a/src/views/AgentChat/components/ChatArea/MessageRenderer.js +++ b/src/views/AgentChat/components/ChatArea/MessageRenderer.js @@ -84,6 +84,7 @@ const MessageRenderer = ({ message, userAvatar }) => { } size="sm" bgGradient="linear(to-br, purple.500, pink.500)" @@ -122,6 +123,7 @@ const MessageRenderer = ({ message, userAvatar }) => { } size="sm" bgGradient="linear(to-br, purple.500, pink.500)" diff --git a/src/views/AgentChat/components/MeetingRoom/MeetingMessageBubble.js b/src/views/AgentChat/components/MeetingRoom/MeetingMessageBubble.js index bad737de..19845976 100644 --- a/src/views/AgentChat/components/MeetingRoom/MeetingMessageBubble.js +++ b/src/views/AgentChat/components/MeetingRoom/MeetingMessageBubble.js @@ -28,14 +28,63 @@ import { Copy, ThumbsUp, ChevronRight, + ChevronDown, Database, Check, Wrench, AlertCircle, + Brain, } from 'lucide-react'; import { getRoleConfig, MEETING_ROLES } from '../../constants/meetingRoles'; import { MarkdownWithCharts } from '@components/ChatBot/MarkdownWithCharts'; +/** + * 解析 deepmoney 格式的内容 + * 格式: 思考过程回答内容 + * + * @param {string} content - 原始内容 + * @returns {{ thinking: string | null, answer: string }} 解析后的内容 + */ +const parseDeepmoneyContent = (content) => { + if (!content) return { thinking: null, answer: '' }; + + // 匹配 ... 标签 + const thinkMatch = content.match(/([\s\S]*?)<\/think>/i); + // 匹配 ... 标签 + const answerMatch = content.match(/([\s\S]*?)<\/answer>/i); + + // 如果有 answer 标签,提取内容 + if (answerMatch) { + return { + thinking: thinkMatch ? thinkMatch[1].trim() : null, + answer: answerMatch[1].trim(), + }; + } + + // 如果只有 think 标签但没有 answer 标签,可能正在流式输出中 + if (thinkMatch && !answerMatch) { + // 检查 think 后面是否有其他内容 + const afterThink = content.replace(/[\s\S]*?<\/think>/i, '').trim(); + // 如果 think 后面有内容但不是 answer 标签包裹的,可能是部分输出 + if (afterThink && !afterThink.startsWith('')) { + return { + thinking: thinkMatch[1].trim(), + answer: afterThink.replace(/<\/?answer>/gi, '').trim(), + }; + } + return { + thinking: thinkMatch[1].trim(), + answer: '', + }; + } + + // 如果没有特殊标签,返回原内容 + return { + thinking: null, + answer: content, + }; +}; + /** * 获取角色图标 */ @@ -281,6 +330,67 @@ const ToolCallsList = ({ toolCalls, roleColor }) => { ); }; +/** + * 思考过程展示组件 + * 用于显示 deepmoney 等模型的思考过程,默认折叠 + */ +const ThinkingBlock = ({ thinking, roleColor }) => { + const [isExpanded, setIsExpanded] = useState(false); + + if (!thinking) return null; + + return ( + + setIsExpanded(!isExpanded)} + p={2} + bg="rgba(255, 255, 255, 0.03)" + borderRadius="md" + border="1px solid" + borderColor="rgba(255, 255, 255, 0.1)" + _hover={{ borderColor: `${roleColor}30` }} + transition="all 0.2s" + > + + + AI 思考过程 + + + + + + + + + + {thinking} + + + + + ); +}; + /** * MeetingMessageBubble - 会议消息气泡组件 * @@ -326,6 +436,7 @@ const MeetingMessageBubble = ({ message, isLatest }) => { > { /> )} - {/* 消息内容 */} - - {message.content ? ( - - ) : isStreaming ? ( - - - 正在思考... - - ) : null} + {/* 解析 deepmoney 格式的内容 */} + {(() => { + const parsedContent = parseDeepmoneyContent(message.content); + return ( + <> + {/* 思考过程(可折叠) */} + - {/* 流式输出时的光标 */} - {isStreaming && message.content && ( - - ▌ - - )} - + {/* 消息内容 */} + + {parsedContent.answer ? ( + + ) : isStreaming ? ( + + + 正在思考... + + ) : null} + + {/* 流式输出时的光标 */} + {isStreaming && parsedContent.answer && ( + + ▌ + + )} + + + ); + })()} {/* 操作按钮 */} diff --git a/src/views/AgentChat/constants/meetingRoles.ts b/src/views/AgentChat/constants/meetingRoles.ts index 5bf8bc5d..a4c2a3d3 100644 --- a/src/views/AgentChat/constants/meetingRoles.ts +++ b/src/views/AgentChat/constants/meetingRoles.ts @@ -122,7 +122,7 @@ export const MEETING_ROLES: Record = { name: '巴菲特', nickname: '唱多者', roleType: 'bull', - avatar: '/avatars/buffett.png', + avatar: '/images/agent/巴菲特.png', color: '#10B981', gradient: 'linear(to-br, green.400, emerald.600)', description: '主观多头,善于分析事件的潜在利好和长期价值', @@ -133,7 +133,7 @@ export const MEETING_ROLES: Record = { name: '大空头', nickname: '大空头', roleType: 'bear', - avatar: '/avatars/big_short.png', + avatar: '/images/agent/大空头.png', color: '#EF4444', gradient: 'linear(to-br, red.400, rose.600)', description: '善于分析事件和财报中的风险因素,帮助投资者避雷', @@ -144,7 +144,7 @@ export const MEETING_ROLES: Record = { name: '量化分析员', nickname: '西蒙斯', roleType: 'quant', - avatar: '/avatars/simons.png', + avatar: '/images/agent/simons.png', color: '#3B82F6', gradient: 'linear(to-br, blue.400, cyan.600)', description: '中性立场,使用量化分析工具分析技术指标', @@ -155,7 +155,7 @@ export const MEETING_ROLES: Record = { name: '韭菜', nickname: '牢大', roleType: 'retail', - avatar: '/avatars/leek.png', + avatar: '/images/agent/牢大.png', color: '#F59E0B', gradient: 'linear(to-br, amber.400, yellow.600)', description: '贪婪又讨厌亏损,热爱追涨杀跌的典型散户', @@ -166,7 +166,7 @@ export const MEETING_ROLES: Record = { name: '基金经理', nickname: '决策者', roleType: 'manager', - avatar: '/avatars/fund_manager.png', + avatar: '/images/agent/基金经理.png', color: '#8B5CF6', gradient: 'linear(to-br, purple.400, violet.600)', description: '总结其他人的发言做出最终决策', diff --git a/src/views/AgentChat/constants/tools.ts b/src/views/AgentChat/constants/tools.ts index 89930d60..5b14ee7e 100644 --- a/src/views/AgentChat/constants/tools.ts +++ b/src/views/AgentChat/constants/tools.ts @@ -220,15 +220,9 @@ export const TOOL_CATEGORIES: Record = { /** * 默认选中的工具 ID 列表 - * 这些工具在页面初始化时自动选中 + * 所有工具在页面初始化时自动选中 */ -export const DEFAULT_SELECTED_TOOLS: string[] = [ - 'search_news', - 'search_china_news', - 'search_concepts', - 'search_limit_up_stocks', - 'search_research_reports', -]; +export const DEFAULT_SELECTED_TOOLS: string[] = MCP_TOOLS.map((tool) => tool.id); /** * 根据 ID 查找工具配置 diff --git a/src/views/AgentChat/hooks/useInvestmentMeeting.ts b/src/views/AgentChat/hooks/useInvestmentMeeting.ts index f6959a24..3862d17f 100644 --- a/src/views/AgentChat/hooks/useInvestmentMeeting.ts +++ b/src/views/AgentChat/hooks/useInvestmentMeeting.ts @@ -357,11 +357,20 @@ export const useInvestmentMeeting = ({ break; case 'message_complete': - if (data.role_id) { - // 后端可能发送 message 对象或直接 content - const finalContent = data.message?.content || data.content; - finishStreamingMessage(data.role_id, finalContent); - setSpeakingRoleId(null); + { + // 后端发送的是 message 对象,role_id 在 message 里 + const roleId = data.role_id || data.message?.role_id; + if (roleId) { + // 后端可能发送 message 对象或直接 content + const finalContent = data.message?.content || data.content; + finishStreamingMessage(roleId, finalContent); + setSpeakingRoleId(null); + + // 如果是结论消息,记录下来 + if (data.message?.is_conclusion) { + setConclusion(data.message); + } + } } break; @@ -562,11 +571,20 @@ export const useInvestmentMeeting = ({ break; case 'message_complete': - if (data.role_id) { - // 后端可能发送 message 对象或直接 content - const finalContent = data.message?.content || data.content; - finishStreamingMessage(data.role_id, finalContent); - setSpeakingRoleId(null); + { + // 后端发送的是 message 对象,role_id 在 message 里 + const roleId = data.role_id || data.message?.role_id; + if (roleId) { + // 后端可能发送 message 对象或直接 content + const finalContent = data.message?.content || data.content; + finishStreamingMessage(roleId, finalContent); + setSpeakingRoleId(null); + + // 如果是结论消息,记录下来 + if (data.message?.is_conclusion) { + setConclusion(data.message); + } + } } break;