update pay function

This commit is contained in:
2025-11-28 16:32:27 +08:00
parent 250d585b87
commit ec2978026a
7 changed files with 212 additions and 73 deletions

View File

@@ -2377,7 +2377,7 @@ MEETING_ROLES = {
"name": "巴菲特", "name": "巴菲特",
"nickname": "唱多者", "nickname": "唱多者",
"role_type": "bull", "role_type": "bull",
"avatar": "/avatars/buffett.png", "avatar": "/images/agent/巴菲特.png",
"model": "kimi-k2-thinking", "model": "kimi-k2-thinking",
"color": "#10B981", "color": "#10B981",
"description": "主观多头,善于分析事件的潜在利好和长期价值", "description": "主观多头,善于分析事件的潜在利好和长期价值",
@@ -2403,7 +2403,7 @@ MEETING_ROLES = {
"name": "大空头", "name": "大空头",
"nickname": "大空头", "nickname": "大空头",
"role_type": "bear", "role_type": "bear",
"avatar": "/avatars/big_short.png", "avatar": "/images/agent/大空头.png",
"model": "kimi-k2-thinking", "model": "kimi-k2-thinking",
"color": "#EF4444", "color": "#EF4444",
"description": "善于分析事件和财报中的风险因素", "description": "善于分析事件和财报中的风险因素",
@@ -2429,7 +2429,7 @@ MEETING_ROLES = {
"name": "量化分析员", "name": "量化分析员",
"nickname": "西蒙斯", "nickname": "西蒙斯",
"role_type": "quant", "role_type": "quant",
"avatar": "/avatars/simons.png", "avatar": "/images/agent/simons.png",
"model": "deepseek", "model": "deepseek",
"color": "#3B82F6", "color": "#3B82F6",
"description": "中性立场,使用量化工具分析技术指标", "description": "中性立场,使用量化工具分析技术指标",
@@ -2454,7 +2454,7 @@ MEETING_ROLES = {
"name": "韭菜", "name": "韭菜",
"nickname": "牢大", "nickname": "牢大",
"role_type": "retail", "role_type": "retail",
"avatar": "/avatars/leek.png", "avatar": "/images/agent/牢大.png",
"model": "deepmoney", "model": "deepmoney",
"color": "#F59E0B", "color": "#F59E0B",
"description": "贪婪又讨厌亏损,热爱追涨杀跌", "description": "贪婪又讨厌亏损,热爱追涨杀跌",
@@ -2474,7 +2474,7 @@ MEETING_ROLES = {
"name": "基金经理", "name": "基金经理",
"nickname": "决策者", "nickname": "决策者",
"role_type": "manager", "role_type": "manager",
"avatar": "/avatars/fund_manager.png", "avatar": "/images/agent/基金经理.png",
"model": "deepseek", "model": "deepseek",
"color": "#8B5CF6", "color": "#8B5CF6",
"description": "综合分析做出最终决策", "description": "综合分析做出最终决策",
@@ -2650,7 +2650,7 @@ async def stream_role_response(
messages=messages, messages=messages,
stream=True, stream=True,
temperature=0.7, temperature=0.7,
max_tokens=500, max_tokens=2000, # 增加 token 限制以避免输出被截断
) )
full_content = "" full_content = ""

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 134 KiB

View File

@@ -84,6 +84,7 @@ const MessageRenderer = ({ message, userAvatar }) => {
<Flex justify="flex-start"> <Flex justify="flex-start">
<HStack align="start" spacing={3} maxW="75%"> <HStack align="start" spacing={3} maxW="75%">
<Avatar <Avatar
src="/images/agent/基金经理.png"
icon={<Cpu className="w-4 h-4" />} icon={<Cpu className="w-4 h-4" />}
size="sm" size="sm"
bgGradient="linear(to-br, purple.500, pink.500)" bgGradient="linear(to-br, purple.500, pink.500)"
@@ -122,6 +123,7 @@ const MessageRenderer = ({ message, userAvatar }) => {
<Flex justify="flex-start"> <Flex justify="flex-start">
<HStack align="start" spacing={3} maxW="75%"> <HStack align="start" spacing={3} maxW="75%">
<Avatar <Avatar
src="/images/agent/基金经理.png"
icon={<Cpu className="w-4 h-4" />} icon={<Cpu className="w-4 h-4" />}
size="sm" size="sm"
bgGradient="linear(to-br, purple.500, pink.500)" bgGradient="linear(to-br, purple.500, pink.500)"

View File

@@ -28,14 +28,63 @@ import {
Copy, Copy,
ThumbsUp, ThumbsUp,
ChevronRight, ChevronRight,
ChevronDown,
Database, Database,
Check, Check,
Wrench, Wrench,
AlertCircle, AlertCircle,
Brain,
} from 'lucide-react'; } from 'lucide-react';
import { getRoleConfig, MEETING_ROLES } from '../../constants/meetingRoles'; import { getRoleConfig, MEETING_ROLES } from '../../constants/meetingRoles';
import { MarkdownWithCharts } from '@components/ChatBot/MarkdownWithCharts'; import { MarkdownWithCharts } from '@components/ChatBot/MarkdownWithCharts';
/**
* 解析 deepmoney 格式的内容
* 格式: <think>思考过程</think><answer>回答内容</answer>
*
* @param {string} content - 原始内容
* @returns {{ thinking: string | null, answer: string }} 解析后的内容
*/
const parseDeepmoneyContent = (content) => {
if (!content) return { thinking: null, answer: '' };
// 匹配 <think>...</think> 标签
const thinkMatch = content.match(/<think>([\s\S]*?)<\/think>/i);
// 匹配 <answer>...</answer> 标签
const answerMatch = content.match(/<answer>([\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(/<think>[\s\S]*?<\/think>/i, '').trim();
// 如果 think 后面有内容但不是 answer 标签包裹的,可能是部分输出
if (afterThink && !afterThink.startsWith('<answer>')) {
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 (
<Box mb={3}>
<HStack
spacing={2}
cursor="pointer"
onClick={() => 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"
>
<Brain className="w-3 h-3" style={{ color: roleColor }} />
<Text fontSize="xs" color="gray.400" flex={1}>
AI 思考过程
</Text>
<Box
color="gray.500"
transition="transform 0.2s"
transform={isExpanded ? 'rotate(180deg)' : 'rotate(0deg)'}
>
<ChevronDown className="w-3 h-3" />
</Box>
</HStack>
<Collapse in={isExpanded} animateOpacity>
<Box
mt={2}
p={3}
bg="rgba(0, 0, 0, 0.2)"
borderRadius="md"
borderLeft="2px solid"
borderColor={`${roleColor}50`}
maxH="200px"
overflowY="auto"
sx={{
'&::-webkit-scrollbar': { width: '4px' },
'&::-webkit-scrollbar-track': { bg: 'transparent' },
'&::-webkit-scrollbar-thumb': { bg: 'gray.600', borderRadius: 'full' },
}}
>
<Text fontSize="xs" color="gray.400" whiteSpace="pre-wrap" lineHeight="tall">
{thinking}
</Text>
</Box>
</Collapse>
</Box>
);
};
/** /**
* MeetingMessageBubble - 会议消息气泡组件 * MeetingMessageBubble - 会议消息气泡组件
* *
@@ -326,6 +436,7 @@ const MeetingMessageBubble = ({ message, isLatest }) => {
> >
<Avatar <Avatar
size="sm" size="sm"
src={roleConfig.avatar}
icon={getRoleIcon(roleConfig.roleType)} icon={getRoleIcon(roleConfig.roleType)}
bg={roleConfig.color} bg={roleConfig.color}
boxShadow={`0 0 12px ${roleConfig.color}40`} boxShadow={`0 0 12px ${roleConfig.color}40`}
@@ -444,6 +555,17 @@ const MeetingMessageBubble = ({ message, isLatest }) => {
/> />
)} )}
{/* 解析 deepmoney 格式的内容 */}
{(() => {
const parsedContent = parseDeepmoneyContent(message.content);
return (
<>
{/* 思考过程(可折叠) */}
<ThinkingBlock
thinking={parsedContent.thinking}
roleColor={roleConfig.color}
/>
{/* 消息内容 */} {/* 消息内容 */}
<Box <Box
fontSize="sm" fontSize="sm"
@@ -469,8 +591,8 @@ const MeetingMessageBubble = ({ message, isLatest }) => {
'& strong': { color: roleConfig.color }, '& strong': { color: roleConfig.color },
}} }}
> >
{message.content ? ( {parsedContent.answer ? (
<MarkdownWithCharts content={message.content} variant="dark" /> <MarkdownWithCharts content={parsedContent.answer} variant="dark" />
) : isStreaming ? ( ) : isStreaming ? (
<HStack spacing={2} color="gray.500"> <HStack spacing={2} color="gray.500">
<Spinner size="sm" /> <Spinner size="sm" />
@@ -479,7 +601,7 @@ const MeetingMessageBubble = ({ message, isLatest }) => {
) : null} ) : null}
{/* 流式输出时的光标 */} {/* 流式输出时的光标 */}
{isStreaming && message.content && ( {isStreaming && parsedContent.answer && (
<motion.span <motion.span
animate={{ opacity: [1, 0, 1] }} animate={{ opacity: [1, 0, 1] }}
transition={{ duration: 0.8, repeat: Infinity }} transition={{ duration: 0.8, repeat: Infinity }}
@@ -489,6 +611,9 @@ const MeetingMessageBubble = ({ message, isLatest }) => {
</motion.span> </motion.span>
)} )}
</Box> </Box>
</>
);
})()}
{/* 操作按钮 */} {/* 操作按钮 */}
<Flex mt={3} pt={3} borderTop="1px solid" borderColor="whiteAlpha.100"> <Flex mt={3} pt={3} borderTop="1px solid" borderColor="whiteAlpha.100">

View File

@@ -122,7 +122,7 @@ export const MEETING_ROLES: Record<string, MeetingRoleConfig> = {
name: '巴菲特', name: '巴菲特',
nickname: '唱多者', nickname: '唱多者',
roleType: 'bull', roleType: 'bull',
avatar: '/avatars/buffett.png', avatar: '/images/agent/巴菲特.png',
color: '#10B981', color: '#10B981',
gradient: 'linear(to-br, green.400, emerald.600)', gradient: 'linear(to-br, green.400, emerald.600)',
description: '主观多头,善于分析事件的潜在利好和长期价值', description: '主观多头,善于分析事件的潜在利好和长期价值',
@@ -133,7 +133,7 @@ export const MEETING_ROLES: Record<string, MeetingRoleConfig> = {
name: '大空头', name: '大空头',
nickname: '大空头', nickname: '大空头',
roleType: 'bear', roleType: 'bear',
avatar: '/avatars/big_short.png', avatar: '/images/agent/大空头.png',
color: '#EF4444', color: '#EF4444',
gradient: 'linear(to-br, red.400, rose.600)', gradient: 'linear(to-br, red.400, rose.600)',
description: '善于分析事件和财报中的风险因素,帮助投资者避雷', description: '善于分析事件和财报中的风险因素,帮助投资者避雷',
@@ -144,7 +144,7 @@ export const MEETING_ROLES: Record<string, MeetingRoleConfig> = {
name: '量化分析员', name: '量化分析员',
nickname: '西蒙斯', nickname: '西蒙斯',
roleType: 'quant', roleType: 'quant',
avatar: '/avatars/simons.png', avatar: '/images/agent/simons.png',
color: '#3B82F6', color: '#3B82F6',
gradient: 'linear(to-br, blue.400, cyan.600)', gradient: 'linear(to-br, blue.400, cyan.600)',
description: '中性立场,使用量化分析工具分析技术指标', description: '中性立场,使用量化分析工具分析技术指标',
@@ -155,7 +155,7 @@ export const MEETING_ROLES: Record<string, MeetingRoleConfig> = {
name: '韭菜', name: '韭菜',
nickname: '牢大', nickname: '牢大',
roleType: 'retail', roleType: 'retail',
avatar: '/avatars/leek.png', avatar: '/images/agent/牢大.png',
color: '#F59E0B', color: '#F59E0B',
gradient: 'linear(to-br, amber.400, yellow.600)', gradient: 'linear(to-br, amber.400, yellow.600)',
description: '贪婪又讨厌亏损,热爱追涨杀跌的典型散户', description: '贪婪又讨厌亏损,热爱追涨杀跌的典型散户',
@@ -166,7 +166,7 @@ export const MEETING_ROLES: Record<string, MeetingRoleConfig> = {
name: '基金经理', name: '基金经理',
nickname: '决策者', nickname: '决策者',
roleType: 'manager', roleType: 'manager',
avatar: '/avatars/fund_manager.png', avatar: '/images/agent/基金经理.png',
color: '#8B5CF6', color: '#8B5CF6',
gradient: 'linear(to-br, purple.400, violet.600)', gradient: 'linear(to-br, purple.400, violet.600)',
description: '总结其他人的发言做出最终决策', description: '总结其他人的发言做出最终决策',

View File

@@ -220,15 +220,9 @@ export const TOOL_CATEGORIES: Record<ToolCategory, MCPTool[]> = {
/** /**
* 默认选中的工具 ID 列表 * 默认选中的工具 ID 列表
* 这些工具在页面初始化时自动选中 * 所有工具在页面初始化时自动选中
*/ */
export const DEFAULT_SELECTED_TOOLS: string[] = [ export const DEFAULT_SELECTED_TOOLS: string[] = MCP_TOOLS.map((tool) => tool.id);
'search_news',
'search_china_news',
'search_concepts',
'search_limit_up_stocks',
'search_research_reports',
];
/** /**
* 根据 ID 查找工具配置 * 根据 ID 查找工具配置

View File

@@ -357,11 +357,20 @@ export const useInvestmentMeeting = ({
break; break;
case 'message_complete': case 'message_complete':
if (data.role_id) { {
// 后端发送的是 message 对象role_id 在 message 里
const roleId = data.role_id || data.message?.role_id;
if (roleId) {
// 后端可能发送 message 对象或直接 content // 后端可能发送 message 对象或直接 content
const finalContent = data.message?.content || data.content; const finalContent = data.message?.content || data.content;
finishStreamingMessage(data.role_id, finalContent); finishStreamingMessage(roleId, finalContent);
setSpeakingRoleId(null); setSpeakingRoleId(null);
// 如果是结论消息,记录下来
if (data.message?.is_conclusion) {
setConclusion(data.message);
}
}
} }
break; break;
@@ -562,11 +571,20 @@ export const useInvestmentMeeting = ({
break; break;
case 'message_complete': case 'message_complete':
if (data.role_id) { {
// 后端发送的是 message 对象role_id 在 message 里
const roleId = data.role_id || data.message?.role_id;
if (roleId) {
// 后端可能发送 message 对象或直接 content // 后端可能发送 message 对象或直接 content
const finalContent = data.message?.content || data.content; const finalContent = data.message?.content || data.content;
finishStreamingMessage(data.role_id, finalContent); finishStreamingMessage(roleId, finalContent);
setSpeakingRoleId(null); setSpeakingRoleId(null);
// 如果是结论消息,记录下来
if (data.message?.is_conclusion) {
setConclusion(data.message);
}
}
} }
break; break;