Files
vf_react/src/views/AgentChat/index.js
2025-11-28 14:49:16 +08:00

237 lines
8.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;