237 lines
8.2 KiB
JavaScript
237 lines
8.2 KiB
JavaScript
// src/views/AgentChat/index.js
|
||
// 超炫酷的 AI 投研助手 - HeroUI v3 现代深色主题版本
|
||
// 使用 Framer Motion 物理动画引擎 + 毛玻璃效果
|
||
// 支持两种模式:单一聊天模式 & 投研会议室模式
|
||
|
||
import React, { useState } from 'react';
|
||
import { Box, Flex, useToast, HStack, Button, Tooltip } from '@chakra-ui/react';
|
||
import { motion, AnimatePresence } from 'framer-motion';
|
||
import { MessageSquare, Users } from 'lucide-react';
|
||
import { useAuth } from '@contexts/AuthContext';
|
||
|
||
// 常量配置 - 从 TypeScript 模块导入
|
||
import { DEFAULT_MODEL_ID } from './constants/models';
|
||
import { DEFAULT_SELECTED_TOOLS } from './constants/tools';
|
||
|
||
// 拆分后的子组件
|
||
import LeftSidebar from './components/LeftSidebar';
|
||
import ChatArea from './components/ChatArea';
|
||
import RightSidebar from './components/RightSidebar';
|
||
import MeetingRoom from './components/MeetingRoom';
|
||
|
||
// 自定义 Hooks
|
||
import { useAgentSessions, useAgentChat, useFileUpload } from './hooks';
|
||
|
||
/**
|
||
* 聊天模式枚举
|
||
*/
|
||
const ChatMode = {
|
||
SINGLE: 'single', // 单一聊天模式
|
||
MEETING: 'meeting', // 投研会议室模式
|
||
};
|
||
|
||
/**
|
||
* Agent Chat - 主组件(HeroUI v3 深色主题)
|
||
*
|
||
* 架构说明:
|
||
* - Phase 1: 常量配置已提取到 constants/ 目录(TypeScript)
|
||
* - Phase 2: UI 组件已拆分到 components/ 目录
|
||
* - Phase 3: 业务逻辑已提取到 hooks/ 目录(TypeScript)
|
||
*
|
||
* 主组件职责:
|
||
* 1. 组合各个自定义 Hooks
|
||
* 2. 管理 UI 状态(侧边栏开关、模型选择、工具选择、聊天模式)
|
||
* 3. 组合渲染子组件
|
||
*
|
||
* 新增功能(2024-11):
|
||
* - 投研会议室模式:多 AI 角色协作讨论投资议题
|
||
*/
|
||
const AgentChat = () => {
|
||
const { user } = useAuth();
|
||
const toast = useToast();
|
||
|
||
// ==================== 聊天模式状态 ====================
|
||
const [chatMode, setChatMode] = useState(ChatMode.SINGLE);
|
||
|
||
// ==================== UI 状态(主组件管理)====================
|
||
const [selectedModel, setSelectedModel] = useState(DEFAULT_MODEL_ID);
|
||
const [selectedTools, setSelectedTools] = useState(DEFAULT_SELECTED_TOOLS);
|
||
const [isLeftSidebarOpen, setIsLeftSidebarOpen] = useState(true);
|
||
const [isRightSidebarOpen, setIsRightSidebarOpen] = useState(true);
|
||
|
||
// ==================== 自定义 Hooks ====================
|
||
|
||
// 文件上传 Hook
|
||
const { uploadedFiles, fileInputRef, handleFileSelect, removeFile, clearFiles } = useFileUpload();
|
||
|
||
// 会话管理 Hook(需要先创建 messages state)
|
||
const [messages, setMessages] = useState([]);
|
||
|
||
const {
|
||
sessions,
|
||
currentSessionId,
|
||
setCurrentSessionId,
|
||
isLoadingSessions,
|
||
loadSessions,
|
||
switchSession,
|
||
createNewSession,
|
||
} = useAgentSessions({
|
||
user,
|
||
setMessages,
|
||
});
|
||
|
||
// 消息处理 Hook
|
||
const {
|
||
inputValue,
|
||
setInputValue,
|
||
isProcessing,
|
||
handleSendMessage,
|
||
handleKeyPress,
|
||
} = useAgentChat({
|
||
user,
|
||
currentSessionId,
|
||
setCurrentSessionId,
|
||
selectedModel,
|
||
selectedTools,
|
||
uploadedFiles,
|
||
clearFiles,
|
||
toast,
|
||
loadSessions,
|
||
messages,
|
||
setMessages,
|
||
});
|
||
|
||
// ==================== 输入框引用(保留在主组件)====================
|
||
const inputRef = React.useRef(null);
|
||
|
||
// ==================== 渲染组件 ====================
|
||
return (
|
||
<Flex h="100%" position="relative" bg="gray.900" direction="column">
|
||
{/* 模式切换栏 */}
|
||
<Box
|
||
bg="rgba(17, 24, 39, 0.95)"
|
||
borderBottom="1px solid"
|
||
borderColor="rgba(255, 255, 255, 0.1)"
|
||
px={4}
|
||
py={2}
|
||
>
|
||
<HStack spacing={2} justify="center">
|
||
<Tooltip label="单一聊天模式:与 AI 助手一对一对话">
|
||
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
||
<Button
|
||
size="sm"
|
||
leftIcon={<MessageSquare className="w-4 h-4" />}
|
||
variant={chatMode === ChatMode.SINGLE ? 'solid' : 'ghost'}
|
||
colorScheme={chatMode === ChatMode.SINGLE ? 'purple' : 'gray'}
|
||
onClick={() => setChatMode(ChatMode.SINGLE)}
|
||
bg={chatMode === ChatMode.SINGLE ? 'purple.500' : 'transparent'}
|
||
color={chatMode === ChatMode.SINGLE ? 'white' : 'gray.400'}
|
||
_hover={{
|
||
bg: chatMode === ChatMode.SINGLE ? 'purple.600' : 'whiteAlpha.100',
|
||
}}
|
||
>
|
||
智能助手
|
||
</Button>
|
||
</motion.div>
|
||
</Tooltip>
|
||
|
||
<Tooltip label="投研会议室:多位 AI 分析师协作讨论投资议题">
|
||
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
||
<Button
|
||
size="sm"
|
||
leftIcon={<Users className="w-4 h-4" />}
|
||
variant={chatMode === ChatMode.MEETING ? 'solid' : 'ghost'}
|
||
colorScheme={chatMode === ChatMode.MEETING ? 'orange' : 'gray'}
|
||
onClick={() => setChatMode(ChatMode.MEETING)}
|
||
bg={chatMode === ChatMode.MEETING ? 'orange.500' : 'transparent'}
|
||
color={chatMode === ChatMode.MEETING ? 'white' : 'gray.400'}
|
||
_hover={{
|
||
bg: chatMode === ChatMode.MEETING ? 'orange.600' : 'whiteAlpha.100',
|
||
}}
|
||
>
|
||
投研会议室
|
||
</Button>
|
||
</motion.div>
|
||
</Tooltip>
|
||
</HStack>
|
||
</Box>
|
||
|
||
{/* 主内容区 */}
|
||
<Flex flex={1} overflow="hidden">
|
||
<AnimatePresence mode="wait">
|
||
{chatMode === ChatMode.SINGLE ? (
|
||
<motion.div
|
||
key="single-chat"
|
||
initial={{ opacity: 0, x: -20 }}
|
||
animate={{ opacity: 1, x: 0 }}
|
||
exit={{ opacity: 0, x: 20 }}
|
||
transition={{ duration: 0.3 }}
|
||
style={{ display: 'flex', flex: 1, height: '100%' }}
|
||
>
|
||
{/* 左侧栏 */}
|
||
<LeftSidebar
|
||
isOpen={isLeftSidebarOpen}
|
||
onClose={() => setIsLeftSidebarOpen(false)}
|
||
sessions={sessions}
|
||
currentSessionId={currentSessionId}
|
||
onSessionSwitch={switchSession}
|
||
onNewSession={createNewSession}
|
||
isLoadingSessions={isLoadingSessions}
|
||
user={user}
|
||
/>
|
||
|
||
{/* 中间聊天区 */}
|
||
<ChatArea
|
||
messages={messages}
|
||
inputValue={inputValue}
|
||
onInputChange={setInputValue}
|
||
isProcessing={isProcessing}
|
||
onSendMessage={handleSendMessage}
|
||
onKeyPress={handleKeyPress}
|
||
uploadedFiles={uploadedFiles}
|
||
onFileSelect={handleFileSelect}
|
||
onFileRemove={removeFile}
|
||
selectedModel={selectedModel}
|
||
isLeftSidebarOpen={isLeftSidebarOpen}
|
||
isRightSidebarOpen={isRightSidebarOpen}
|
||
onToggleLeftSidebar={() => setIsLeftSidebarOpen(true)}
|
||
onToggleRightSidebar={() => setIsRightSidebarOpen(true)}
|
||
onNewSession={createNewSession}
|
||
userAvatar={user?.avatar}
|
||
inputRef={inputRef}
|
||
fileInputRef={fileInputRef}
|
||
/>
|
||
|
||
{/* 右侧栏 */}
|
||
<RightSidebar
|
||
isOpen={isRightSidebarOpen}
|
||
onClose={() => setIsRightSidebarOpen(false)}
|
||
selectedModel={selectedModel}
|
||
onModelChange={setSelectedModel}
|
||
selectedTools={selectedTools}
|
||
onToolsChange={setSelectedTools}
|
||
sessionsCount={sessions.length}
|
||
messagesCount={messages.length}
|
||
/>
|
||
</motion.div>
|
||
) : (
|
||
<motion.div
|
||
key="meeting-room"
|
||
initial={{ opacity: 0, x: 20 }}
|
||
animate={{ opacity: 1, x: 0 }}
|
||
exit={{ opacity: 0, x: -20 }}
|
||
transition={{ duration: 0.3 }}
|
||
style={{ flex: 1, height: '100%' }}
|
||
>
|
||
{/* 投研会议室 */}
|
||
<MeetingRoom user={user} onToast={toast} />
|
||
</motion.div>
|
||
)}
|
||
</AnimatePresence>
|
||
</Flex>
|
||
</Flex>
|
||
);
|
||
};
|
||
|
||
export default AgentChat;
|