update pay function

This commit is contained in:
2025-11-22 11:49:20 +08:00
parent 42b7d2ee63
commit 47be4584f9

View File

@@ -1,6 +1,6 @@
// src/views/AgentChat/index.js
// 超炫酷的 AI 投研助手 - Hero UI 版本
// 模仿 Google AI Studio 风格
// 使用 CSS 动画替代 Framer Motion避免版本冲突
import React, { useState, useEffect, useRef } from 'react';
import {
@@ -22,20 +22,15 @@ import {
Tab,
ScrollShadow,
Kbd,
Snippet,
Accordion,
AccordionItem,
} from '@heroui/react';
import { motion, AnimatePresence } from 'framer-motion';
import { useAuth } from '@contexts/AuthContext';
import { logger } from '@utils/logger';
import axios from 'axios';
import { useToast } from '@chakra-ui/react'; // 继续使用 Chakra 的 Toast
import { useToast } from '@chakra-ui/react';
// Framer Motion 包装组件
const MotionDiv = motion.div;
// 图标 - 使用 Hero UI 推荐的 Lucide Icons
// 图标 - 使用 Lucide Icons
import {
Send,
Plus,
@@ -156,16 +151,13 @@ const AgentChat = () => {
// ==================== 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);
}
@@ -176,15 +168,12 @@ const AgentChat = () => {
}
};
// 加载会话历史
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) => ({
@@ -195,7 +184,6 @@ const AgentChat = () => {
stepResults: msg.steps ? JSON.parse(msg.steps) : null,
timestamp: msg.timestamp,
}));
setMessages(formattedMessages);
}
} catch (error) {
@@ -203,7 +191,6 @@ const AgentChat = () => {
}
};
// 创建新会话
const createNewSession = () => {
setCurrentSessionId(null);
setMessages([
@@ -216,13 +203,11 @@ const AgentChat = () => {
]);
};
// 切换会话
const switchSession = (sessionId) => {
setCurrentSessionId(sessionId);
loadSessionHistory(sessionId);
};
// 发送消息
const handleSendMessage = async () => {
if (!inputValue.trim() || isProcessing) return;
@@ -238,14 +223,12 @@ const AgentChat = () => {
setIsProcessing(true);
try {
// 显示思考状态
addMessage({
type: MessageTypes.AGENT_THINKING,
content: '正在分析你的问题...',
timestamp: new Date().toISOString(),
});
// 调用后端 API
const response = await axios.post('/mcp/agent/chat', {
message: userInput,
conversation_history: messages
@@ -262,17 +245,14 @@ const AgentChat = () => {
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,
@@ -282,7 +262,6 @@ const AgentChat = () => {
});
}
// 显示执行步骤
if (data.steps && data.steps.length > 0) {
addMessage({
type: MessageTypes.AGENT_EXECUTING,
@@ -293,10 +272,8 @@ const AgentChat = () => {
});
}
// 移除执行中消息
setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_EXECUTING));
// 显示最终结果
addMessage({
type: MessageTypes.AGENT_RESPONSE,
content: data.final_answer || data.message || '处理完成',
@@ -310,7 +287,6 @@ const AgentChat = () => {
}
} catch (error) {
logger.error('Agent chat error', error);
setMessages((prev) =>
prev.filter(
(m) => m.type !== MessageTypes.AGENT_THINKING && m.type !== MessageTypes.AGENT_EXECUTING
@@ -318,7 +294,6 @@ const AgentChat = () => {
);
const errorMessage = error.response?.data?.error || error.message || '处理失败';
addMessage({
type: MessageTypes.ERROR,
content: `处理失败:${errorMessage}`,
@@ -338,12 +313,10 @@ const AgentChat = () => {
}
};
// 添加消息
const addMessage = (message) => {
setMessages((prev) => [...prev, { ...message, id: Date.now() + Math.random() }]);
};
// 自动滚动到底部
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
@@ -352,7 +325,6 @@ const AgentChat = () => {
scrollToBottom();
}, [messages]);
// 处理键盘事件
const handleKeyPress = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
@@ -360,7 +332,6 @@ const AgentChat = () => {
}
};
// 初始化
useEffect(() => {
if (user) {
loadSessions();
@@ -368,13 +339,11 @@ const AgentChat = () => {
}
}, [user]);
// 筛选会话
const filteredSessions = sessions.filter(
(session) =>
!searchQuery || session.last_message?.toLowerCase().includes(searchQuery.toLowerCase())
);
// 快捷问题
const quickQuestions = [
{ text: '全面分析贵州茅台', emoji: '📊' },
{ text: '今日涨停股票分析', emoji: '🔥' },
@@ -384,17 +353,9 @@ const AgentChat = () => {
return (
<div className="flex h-screen bg-gradient-to-br from-gray-50 to-blue-50 dark:from-gray-900 dark:to-gray-800 overflow-hidden">
{/* 左侧栏 - 历史记录 */}
<AnimatePresence>
{/* 左侧栏 */}
{isLeftSidebarOpen && (
<MotionDiv
initial={{ x: -320, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -320, opacity: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
className="w-80 flex flex-col bg-white/80 dark:bg-gray-800/80 backdrop-blur-xl border-r border-gray-200 dark:border-gray-700 shadow-xl"
>
{/* 侧边栏头部 */}
<div className="w-80 flex flex-col bg-white/80 dark:bg-gray-800/80 backdrop-blur-xl border-r border-gray-200 dark:border-gray-700 shadow-xl animate-slide-in-left">
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
@@ -431,7 +392,6 @@ const AgentChat = () => {
/>
</div>
{/* 会话列表 */}
<ScrollShadow className="flex-1 p-2">
{isLoadingSessions ? (
<div className="flex items-center justify-center h-40">
@@ -444,18 +404,13 @@ const AgentChat = () => {
</div>
) : (
<div className="space-y-2">
{filteredSessions.map((session, index) => (
<MotionDiv
key={session.session_id}
initial={{ x: -20, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
transition={{ delay: index * 0.05 }}
>
{filteredSessions.map((session) => (
<Card
key={session.session_id}
isPressable
isHoverable
onPress={() => switchSession(session.session_id)}
className={`${
className={`transition-all ${
currentSessionId === session.session_id
? 'bg-primary-50 dark:bg-primary-900/20 border-primary-500'
: ''
@@ -488,13 +443,11 @@ const AgentChat = () => {
</div>
</CardBody>
</Card>
</MotionDiv>
))}
</div>
)}
</ScrollShadow>
{/* 用户信息 */}
<div className="p-4 border-t border-gray-200 dark:border-gray-700">
<div className="flex items-center gap-3">
<Avatar src={user?.avatar} name={user?.nickname} size="sm" />
@@ -504,18 +457,12 @@ const AgentChat = () => {
</div>
</div>
</div>
</MotionDiv>
</div>
)}
</AnimatePresence>
{/* 中间主聊天区域 */}
<div className="flex-1 flex flex-col">
{/* 聊天头部 */}
<MotionDiv
initial={{ y: -20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
className="bg-white/70 dark:bg-gray-800/70 backdrop-blur-xl border-b border-gray-200 dark:border-gray-700 px-6 py-4 shadow-sm"
>
<div className="bg-white/70 dark:bg-gray-800/70 backdrop-blur-xl border-b border-gray-200 dark:border-gray-700 px-6 py-4 shadow-sm">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
{!isLeftSidebarOpen && (
@@ -529,17 +476,12 @@ const AgentChat = () => {
</Button>
)}
<MotionDiv
animate={{ rotate: [0, 360] }}
transition={{ duration: 3, repeat: Infinity, ease: 'linear' }}
>
<Avatar
icon={<Cpu className="w-6 h-6" />}
classNames={{
base: 'bg-gradient-to-br from-purple-500 to-pink-500',
base: 'bg-gradient-to-br from-purple-500 to-pink-500 animate-spin-slow',
}}
/>
</MotionDiv>
<div>
<h1 className="text-xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
@@ -579,35 +521,21 @@ const AgentChat = () => {
)}
</div>
</div>
</MotionDiv>
</div>
{/* 消息列表 */}
<ScrollShadow className="flex-1 p-6">
<div className="max-w-4xl mx-auto space-y-4">
<AnimatePresence>
{messages.map((message, index) => (
<MotionDiv
key={message.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ delay: index * 0.1 }}
>
{messages.map((message) => (
<div key={message.id} className="animate-fade-in">
<MessageRenderer message={message} userAvatar={user?.avatar} />
</MotionDiv>
</div>
))}
</AnimatePresence>
<div ref={messagesEndRef} />
</div>
</ScrollShadow>
{/* 快捷问题 */}
{messages.length <= 2 && !isProcessing && (
<MotionDiv
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
className="px-6 py-3"
>
<div className="px-6 py-3 animate-fade-in">
<div className="max-w-4xl mx-auto">
<p className="text-xs text-gray-500 mb-2 font-medium flex items-center gap-1">
<Sparkles className="w-3 h-3" />
@@ -615,11 +543,11 @@ const AgentChat = () => {
</p>
<div className="grid grid-cols-2 gap-2">
{quickQuestions.map((question, idx) => (
<MotionDiv key={idx} whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
<Button
key={idx}
variant="bordered"
color="primary"
className="w-full justify-start h-auto py-3"
className="w-full justify-start h-auto py-3 hover:scale-105 transition-transform"
onPress={() => {
setInputValue(question.text);
inputRef.current?.focus();
@@ -628,19 +556,13 @@ const AgentChat = () => {
<span className="mr-2">{question.emoji}</span>
{question.text}
</Button>
</MotionDiv>
))}
</div>
</div>
</MotionDiv>
</div>
)}
{/* 输入框 */}
<MotionDiv
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
className="bg-white/70 dark:bg-gray-800/70 backdrop-blur-xl border-t border-gray-200 dark:border-gray-700 px-6 py-4"
>
<div className="bg-white/70 dark:bg-gray-800/70 backdrop-blur-xl border-t border-gray-200 dark:border-gray-700 px-6 py-4">
<div className="max-w-4xl mx-auto">
<div className="flex gap-2">
<Input
@@ -654,10 +576,9 @@ const AgentChat = () => {
variant="bordered"
classNames={{
input: 'text-base',
inputWrapper: 'border-2 hover:border-primary-500',
inputWrapper: 'border-2 hover:border-primary-500 transition-colors',
}}
/>
<MotionDiv whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
<Button
isIconOnly
color="primary"
@@ -666,14 +587,12 @@ const AgentChat = () => {
onPress={handleSendMessage}
isLoading={isProcessing}
isDisabled={!inputValue.trim() || isProcessing}
className="bg-gradient-to-r from-blue-500 to-purple-500"
className="bg-gradient-to-r from-blue-500 to-purple-500 hover:scale-105 transition-transform"
>
{!isProcessing && <Send className="w-5 h-5" />}
</Button>
</MotionDiv>
</div>
{/* 键盘提示 */}
<div className="flex items-center gap-4 mt-2 text-xs text-gray-500">
<div className="flex items-center gap-1">
<Kbd keys={['enter']}>Enter</Kbd>
@@ -687,20 +606,12 @@ const AgentChat = () => {
</div>
</div>
</div>
</MotionDiv>
</div>
</div>
{/* 右侧栏 - 模型和工具配置 */}
<AnimatePresence>
{/* 右侧栏 */}
{isRightSidebarOpen && (
<MotionDiv
initial={{ x: 320, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: 320, opacity: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
className="w-80 flex flex-col bg-white/80 dark:bg-gray-800/80 backdrop-blur-xl border-l border-gray-200 dark:border-gray-700 shadow-xl"
>
{/* 侧边栏头部 */}
<div className="w-80 flex flex-col bg-white/80 dark:bg-gray-800/80 backdrop-blur-xl border-l border-gray-200 dark:border-gray-700 shadow-xl animate-slide-in-right">
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
@@ -718,21 +629,16 @@ const AgentChat = () => {
</div>
</div>
{/* 配置面板 */}
<ScrollShadow className="flex-1 p-4">
<Tabs aria-label="配置选项" variant="underlined">
<Tab key="model" title="模型">
<div className="space-y-3 mt-4">
{AVAILABLE_MODELS.map((model) => (
<MotionDiv
key={model.id}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
<Card
key={model.id}
isPressable
onPress={() => setSelectedModel(model.id)}
className={`${
className={`hover:scale-102 transition-transform ${
selectedModel === model.id
? 'border-2 border-' + model.color + '-500 bg-' + model.color + '-50'
: 'border-2 border-transparent'
@@ -753,7 +659,6 @@ const AgentChat = () => {
</div>
</CardBody>
</Card>
</MotionDiv>
))}
</div>
</Tab>
@@ -764,14 +669,12 @@ const AgentChat = () => {
<CheckboxGroup value={selectedTools} onValueChange={setSelectedTools}>
<div className="space-y-2">
{AVAILABLE_TOOLS.map((tool) => (
<MotionDiv key={tool.id} whileHover={{ x: 4 }}>
<Checkbox value={tool.id} size="sm">
<Checkbox key={tool.id} value={tool.id} size="sm">
<div className="flex items-center gap-2">
{tool.icon}
<span className="text-sm">{tool.name}</span>
</div>
</Checkbox>
</MotionDiv>
))}
</div>
</CheckboxGroup>
@@ -806,15 +709,82 @@ const AgentChat = () => {
</Tab>
</Tabs>
</ScrollShadow>
</MotionDiv>
</div>
)}
</AnimatePresence>
<style>{`
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slide-in-left {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slide-in-right {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes spin-slow {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.animate-fade-in {
animation: fade-in 0.3s ease-out;
}
.animate-slide-in-left {
animation: slide-in-left 0.3s ease-out;
}
.animate-slide-in-right {
animation: slide-in-right 0.3s ease-out;
}
.animate-spin-slow {
animation: spin-slow 3s linear infinite;
}
.hover\\:scale-102:hover {
transform: scale(1.02);
}
.hover\\:scale-105:hover {
transform: scale(1.05);
}
`}</style>
</div>
);
};
/**
* 消息渲染器组件
* 消息渲染器
*/
const MessageRenderer = ({ message, userAvatar }) => {
switch (message.type) {
@@ -822,13 +792,11 @@ const MessageRenderer = ({ message, userAvatar }) => {
return (
<div className="flex justify-end">
<div className="flex items-start gap-3 max-w-[75%]">
<MotionDiv whileHover={{ scale: 1.02 }}>
<Card className="bg-gradient-to-br from-blue-500 to-purple-500" shadow="lg">
<Card className="bg-gradient-to-br from-blue-500 to-purple-500 hover:scale-102 transition-transform" shadow="lg">
<CardBody className="px-5 py-3">
<p className="text-sm text-white whitespace-pre-wrap">{message.content}</p>
</CardBody>
</Card>
</MotionDiv>
<Avatar src={userAvatar} icon={<User className="w-4 h-4" />} size="sm" />
</div>
</div>
@@ -840,15 +808,9 @@ const MessageRenderer = ({ message, userAvatar }) => {
<div className="flex items-start gap-3 max-w-[75%]">
<Avatar
icon={<Cpu className="w-4 h-4" />}
classNames={{
base: 'bg-gradient-to-br from-purple-500 to-pink-500',
}}
classNames={{ base: 'bg-gradient-to-br from-purple-500 to-pink-500' }}
size="sm"
/>
<MotionDiv
animate={{ scale: [1, 1.02, 1] }}
transition={{ duration: 1.5, repeat: Infinity }}
>
<Card shadow="sm" variant="bordered">
<CardBody className="px-5 py-3">
<div className="flex items-center gap-3">
@@ -859,7 +821,6 @@ const MessageRenderer = ({ message, userAvatar }) => {
</div>
</CardBody>
</Card>
</MotionDiv>
</div>
</div>
);
@@ -870,18 +831,14 @@ const MessageRenderer = ({ message, userAvatar }) => {
<div className="flex items-start gap-3 max-w-[85%]">
<Avatar
icon={<Cpu className="w-4 h-4" />}
classNames={{
base: 'bg-gradient-to-br from-blue-500 to-purple-500',
}}
classNames={{ base: 'bg-gradient-to-br from-blue-500 to-purple-500' }}
size="sm"
/>
<div className="flex-1 space-y-3">
<MotionDiv whileHover={{ scale: 1.01 }}>
<Card shadow="lg">
<Card shadow="lg" className="hover:scale-101 transition-transform">
<CardBody className="px-5 py-4">
<p className="text-sm whitespace-pre-wrap leading-relaxed">{message.content}</p>
{/* 操作按钮 */}
<div className="flex items-center gap-2 mt-3 pt-3 border-t border-gray-200">
<Tooltip content="复制回答">
<Button
@@ -904,7 +861,6 @@ const MessageRenderer = ({ message, userAvatar }) => {
</Button>
</Tooltip>
{/* 元数据 */}
{message.metadata && (
<div className="flex-1 flex items-center justify-end gap-3 text-xs text-gray-500">
<span>步骤: {message.metadata.total_steps}</span>
@@ -918,9 +874,7 @@ const MessageRenderer = ({ message, userAvatar }) => {
</div>
</CardBody>
</Card>
</MotionDiv>
{/* 执行步骤(可展开) */}
{message.stepResults && message.stepResults.length > 0 && (
<StepResultsPanel stepResults={message.stepResults} />
)}
@@ -938,16 +892,11 @@ const MessageRenderer = ({ message, userAvatar }) => {
classNames={{ base: 'bg-danger-500' }}
size="sm"
/>
<MotionDiv
animate={{ x: [-2, 2, -2, 2, 0] }}
transition={{ duration: 0.4 }}
>
<Card className="border-2 border-danger-500 bg-danger-50">
<CardBody className="px-5 py-3">
<p className="text-sm text-danger-700">{message.content}</p>
</CardBody>
</Card>
</MotionDiv>
</div>
</div>
);
@@ -958,7 +907,7 @@ const MessageRenderer = ({ message, userAvatar }) => {
};
/**
* 步骤结果面板组件
* 步骤结果面板
*/
const StepResultsPanel = ({ stepResults }) => {
return (
@@ -975,13 +924,8 @@ const StepResultsPanel = ({ stepResults }) => {
>
<div className="space-y-2">
{stepResults.map((result, idx) => (
<MotionDiv
key={idx}
initial={{ x: -20, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
transition={{ delay: idx * 0.1 }}
>
<Card
key={idx}
className={`border-l-4 ${
result.status === 'success'
? 'border-l-success-500 bg-success-50'
@@ -1012,12 +956,9 @@ const StepResultsPanel = ({ stepResults }) => {
</div>
<p className="text-xs text-gray-500">{result.execution_time?.toFixed(2)}s</p>
</div>
{result.error && (
<p className="text-xs text-danger-600 mt-1"> {result.error}</p>
)}
{result.error && <p className="text-xs text-danger-600 mt-1"> {result.error}</p>}
</CardBody>
</Card>
</MotionDiv>
))}
</div>
</AccordionItem>