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