From f536d68753cbc25daa9b7622b633c6d8b8133e99 Mon Sep 17 00:00:00 2001
From: zdl <3489966805@qq.com>
Date: Mon, 24 Nov 2025 15:18:32 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9B=E5=BB=BA=20ChatArea=20?=
=?UTF-8?q?=E7=BB=84=E4=BB=B6=EF=BC=88=E5=90=AB=20MessageRenderer=E3=80=81?=
=?UTF-8?q?ExecutionStepsDisplay=20=E5=AD=90=E7=BB=84=E4=BB=B6=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ChatArea/ExecutionStepsDisplay.js | 117 +++++
.../components/ChatArea/MessageRenderer.js | 248 +++++++++
.../AgentChat/components/ChatArea/index.js | 474 ++++++++++++++++++
3 files changed, 839 insertions(+)
create mode 100644 src/views/AgentChat/components/ChatArea/ExecutionStepsDisplay.js
create mode 100644 src/views/AgentChat/components/ChatArea/MessageRenderer.js
create mode 100644 src/views/AgentChat/components/ChatArea/index.js
diff --git a/src/views/AgentChat/components/ChatArea/ExecutionStepsDisplay.js b/src/views/AgentChat/components/ChatArea/ExecutionStepsDisplay.js
new file mode 100644
index 00000000..6b9321c2
--- /dev/null
+++ b/src/views/AgentChat/components/ChatArea/ExecutionStepsDisplay.js
@@ -0,0 +1,117 @@
+// src/views/AgentChat/components/ChatArea/ExecutionStepsDisplay.js
+// 执行步骤显示组件
+
+import React from 'react';
+import { motion } from 'framer-motion';
+import {
+ Accordion,
+ AccordionItem,
+ AccordionButton,
+ AccordionPanel,
+ AccordionIcon,
+ Card,
+ CardBody,
+ Badge,
+ HStack,
+ VStack,
+ Flex,
+ Text,
+} from '@chakra-ui/react';
+import { Activity } from 'lucide-react';
+
+/**
+ * ExecutionStepsDisplay - 执行步骤显示组件
+ *
+ * @param {Object} props
+ * @param {Array} props.steps - 执行步骤列表
+ * @param {Object} props.plan - 执行计划(可选)
+ * @returns {JSX.Element}
+ */
+const ExecutionStepsDisplay = ({ steps, plan }) => {
+ return (
+
+
+
+
+
+
+ 执行详情
+
+
+ {steps.length} 步骤
+
+
+
+
+
+
+ {steps.map((result, idx) => (
+
+
+
+
+
+ 步骤 {idx + 1}: {result.tool_name}
+
+
+ {result.status}
+
+
+
+ {result.execution_time?.toFixed(2)}s
+
+ {result.error && (
+
+ ⚠️ {result.error}
+
+ )}
+
+
+
+ ))}
+
+
+
+
+ );
+};
+
+export default ExecutionStepsDisplay;
diff --git a/src/views/AgentChat/components/ChatArea/MessageRenderer.js b/src/views/AgentChat/components/ChatArea/MessageRenderer.js
new file mode 100644
index 00000000..f056b9f3
--- /dev/null
+++ b/src/views/AgentChat/components/ChatArea/MessageRenderer.js
@@ -0,0 +1,248 @@
+// src/views/AgentChat/components/ChatArea/MessageRenderer.js
+// 消息渲染器组件
+
+import React from 'react';
+import { motion } from 'framer-motion';
+import {
+ Card,
+ CardBody,
+ Avatar,
+ Badge,
+ Spinner,
+ Tooltip,
+ IconButton,
+ HStack,
+ Flex,
+ Text,
+ Box,
+} from '@chakra-ui/react';
+import { Cpu, User, Copy, ThumbsUp, ThumbsDown, File } from 'lucide-react';
+import { MessageTypes } from '../../constants/messageTypes';
+import ExecutionStepsDisplay from './ExecutionStepsDisplay';
+
+/**
+ * MessageRenderer - 消息渲染器组件
+ *
+ * @param {Object} props
+ * @param {Object} props.message - 消息对象
+ * @param {string} props.userAvatar - 用户头像 URL
+ * @returns {JSX.Element|null}
+ */
+const MessageRenderer = ({ message, userAvatar }) => {
+ switch (message.type) {
+ case MessageTypes.USER:
+ return (
+
+
+
+
+
+
+ {message.content}
+
+ {message.files && message.files.length > 0 && (
+
+ {message.files.map((file, idx) => (
+
+
+ {file.name}
+
+ ))}
+
+ )}
+
+
+
+ }
+ size="sm"
+ bgGradient="linear(to-br, blue.500, purple.600)"
+ boxShadow="0 0 12px rgba(139, 92, 246, 0.4)"
+ />
+
+
+ );
+
+ case MessageTypes.AGENT_THINKING:
+ return (
+
+
+ }
+ size="sm"
+ bgGradient="linear(to-br, purple.500, pink.500)"
+ boxShadow="0 0 12px rgba(236, 72, 153, 0.4)"
+ />
+
+
+
+
+
+
+ {message.content}
+
+
+
+
+
+
+
+ );
+
+ case MessageTypes.AGENT_RESPONSE:
+ return (
+
+
+ }
+ size="sm"
+ bgGradient="linear(to-br, purple.500, pink.500)"
+ boxShadow="0 0 12px rgba(236, 72, 153, 0.4)"
+ />
+
+
+
+
+ {message.content}
+
+
+ {message.stepResults && message.stepResults.length > 0 && (
+
+
+
+ )}
+
+
+
+
+ }
+ onClick={() => navigator.clipboard.writeText(message.content)}
+ bg="rgba(255, 255, 255, 0.05)"
+ color="gray.400"
+ _hover={{
+ color: 'white',
+ bg: 'rgba(255, 255, 255, 0.1)',
+ }}
+ />
+
+
+
+
+ }
+ bg="rgba(255, 255, 255, 0.05)"
+ color="gray.400"
+ _hover={{
+ color: 'green.400',
+ bg: 'rgba(16, 185, 129, 0.1)',
+ boxShadow: '0 0 12px rgba(16, 185, 129, 0.3)',
+ }}
+ />
+
+
+
+
+ }
+ bg="rgba(255, 255, 255, 0.05)"
+ color="gray.400"
+ _hover={{
+ color: 'red.400',
+ bg: 'rgba(239, 68, 68, 0.1)',
+ boxShadow: '0 0 12px rgba(239, 68, 68, 0.3)',
+ }}
+ />
+
+
+
+ {new Date(message.timestamp).toLocaleTimeString('zh-CN', {
+ hour: '2-digit',
+ minute: '2-digit',
+ })}
+
+
+
+
+
+
+
+ );
+
+ case MessageTypes.ERROR:
+ return (
+
+
+
+
+
+ {message.content}
+
+
+
+
+
+ );
+
+ default:
+ return null;
+ }
+};
+
+export default MessageRenderer;
diff --git a/src/views/AgentChat/components/ChatArea/index.js b/src/views/AgentChat/components/ChatArea/index.js
new file mode 100644
index 00000000..4d44e8ce
--- /dev/null
+++ b/src/views/AgentChat/components/ChatArea/index.js
@@ -0,0 +1,474 @@
+// src/views/AgentChat/components/ChatArea/index.js
+// 中间聊天区域组件
+
+import React from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import {
+ Box,
+ Button,
+ Input,
+ Avatar,
+ Badge,
+ Tooltip,
+ IconButton,
+ Kbd,
+ HStack,
+ VStack,
+ Flex,
+ Text,
+ Tag,
+ TagLabel,
+ TagCloseButton,
+} from '@chakra-ui/react';
+import {
+ Send,
+ Menu,
+ RefreshCw,
+ Settings,
+ Cpu,
+ Zap,
+ Sparkles,
+ Paperclip,
+ Image as ImageIcon,
+} from 'lucide-react';
+import { AVAILABLE_MODELS } from '../../constants/models';
+import { quickQuestions } from '../../constants/quickQuestions';
+import { animations } from '../../constants/animations';
+import MessageRenderer from './MessageRenderer';
+
+/**
+ * ChatArea - 中间聊天区域组件
+ *
+ * @param {Object} props
+ * @param {Array} props.messages - 消息列表
+ * @param {string} props.inputValue - 输入框内容
+ * @param {Function} props.onInputChange - 输入框变化回调
+ * @param {boolean} props.isProcessing - 处理中状态
+ * @param {Function} props.onSendMessage - 发送消息回调
+ * @param {Function} props.onKeyPress - 键盘事件回调
+ * @param {Array} props.uploadedFiles - 已上传文件列表
+ * @param {Function} props.onFileSelect - 文件选择回调
+ * @param {Function} props.onFileRemove - 文件删除回调
+ * @param {string} props.selectedModel - 当前选中的模型 ID
+ * @param {boolean} props.isLeftSidebarOpen - 左侧栏是否展开
+ * @param {boolean} props.isRightSidebarOpen - 右侧栏是否展开
+ * @param {Function} props.onToggleLeftSidebar - 切换左侧栏回调
+ * @param {Function} props.onToggleRightSidebar - 切换右侧栏回调
+ * @param {Function} props.onNewSession - 新建会话回调
+ * @param {string} props.userAvatar - 用户头像 URL
+ * @param {RefObject} props.messagesEndRef - 消息列表底部引用
+ * @param {RefObject} props.inputRef - 输入框引用
+ * @param {RefObject} props.fileInputRef - 文件上传输入引用
+ * @returns {JSX.Element}
+ */
+const ChatArea = ({
+ messages,
+ inputValue,
+ onInputChange,
+ isProcessing,
+ onSendMessage,
+ onKeyPress,
+ uploadedFiles,
+ onFileSelect,
+ onFileRemove,
+ selectedModel,
+ isLeftSidebarOpen,
+ isRightSidebarOpen,
+ onToggleLeftSidebar,
+ onToggleRightSidebar,
+ onNewSession,
+ userAvatar,
+ messagesEndRef,
+ inputRef,
+ fileInputRef,
+}) => {
+ return (
+
+ {/* 顶部标题栏 - 深色毛玻璃 */}
+
+
+
+ {!isLeftSidebarOpen && (
+
+ }
+ onClick={onToggleLeftSidebar}
+ bg="rgba(255, 255, 255, 0.05)"
+ color="gray.400"
+ backdropFilter="blur(10px)"
+ border="1px solid"
+ borderColor="rgba(255, 255, 255, 0.1)"
+ _hover={{
+ bg: 'rgba(255, 255, 255, 0.1)',
+ color: 'white',
+ }}
+ />
+
+ )}
+
+
+ }
+ bgGradient="linear(to-br, purple.500, pink.500)"
+ boxShadow="0 0 20px rgba(236, 72, 153, 0.5)"
+ />
+
+
+
+
+ 价小前投研 AI
+
+
+
+
+ 智能分析
+
+
+ {AVAILABLE_MODELS.find((m) => m.id === selectedModel)?.name}
+
+
+
+
+
+
+
+
+ }
+ onClick={onNewSession}
+ bg="rgba(255, 255, 255, 0.05)"
+ color="gray.400"
+ backdropFilter="blur(10px)"
+ border="1px solid"
+ borderColor="rgba(255, 255, 255, 0.1)"
+ _hover={{
+ bg: 'rgba(255, 255, 255, 0.1)',
+ color: 'white',
+ borderColor: 'purple.400',
+ boxShadow: '0 0 12px rgba(139, 92, 246, 0.3)',
+ }}
+ />
+
+
+ {!isRightSidebarOpen && (
+
+ }
+ onClick={onToggleRightSidebar}
+ bg="rgba(255, 255, 255, 0.05)"
+ color="gray.400"
+ backdropFilter="blur(10px)"
+ border="1px solid"
+ borderColor="rgba(255, 255, 255, 0.1)"
+ _hover={{
+ bg: 'rgba(255, 255, 255, 0.1)',
+ color: 'white',
+ }}
+ />
+
+ )}
+
+
+
+
+ {/* 消息列表 */}
+
+
+
+
+ {messages.map((message) => (
+
+
+
+ ))}
+
+
+
+
+
+
+ {/* 快捷问题 */}
+
+ {messages.length <= 2 && !isProcessing && (
+
+
+
+
+
+ 快速开始
+
+
+ {quickQuestions.map((question, idx) => (
+
+
+
+ ))}
+
+
+
+
+ )}
+
+
+ {/* 输入栏 - 深色毛玻璃 */}
+
+
+ {/* 已上传文件预览 */}
+ {uploadedFiles.length > 0 && (
+
+ {uploadedFiles.map((file, idx) => (
+
+
+ {file.name}
+ onFileRemove(idx)} color="gray.400" />
+
+
+ ))}
+
+ )}
+
+
+
+
+
+
+ }
+ onClick={() => fileInputRef.current?.click()}
+ bg="rgba(255, 255, 255, 0.05)"
+ color="gray.300"
+ backdropFilter="blur(10px)"
+ border="1px solid"
+ borderColor="rgba(255, 255, 255, 0.1)"
+ _hover={{
+ bg: 'rgba(255, 255, 255, 0.1)',
+ borderColor: 'purple.400',
+ color: 'white',
+ boxShadow: '0 0 12px rgba(139, 92, 246, 0.3)',
+ }}
+ />
+
+
+
+
+
+ }
+ onClick={() => {
+ fileInputRef.current?.setAttribute('accept', 'image/*');
+ fileInputRef.current?.click();
+ }}
+ bg="rgba(255, 255, 255, 0.05)"
+ color="gray.300"
+ backdropFilter="blur(10px)"
+ border="1px solid"
+ borderColor="rgba(255, 255, 255, 0.1)"
+ _hover={{
+ bg: 'rgba(255, 255, 255, 0.1)',
+ borderColor: 'purple.400',
+ color: 'white',
+ boxShadow: '0 0 12px rgba(139, 92, 246, 0.3)',
+ }}
+ />
+
+
+
+ onInputChange(e.target.value)}
+ onKeyDown={onKeyPress}
+ placeholder="输入你的问题... (Enter 发送, Shift+Enter 换行)"
+ isDisabled={isProcessing}
+ size="lg"
+ variant="outline"
+ borderWidth={2}
+ bg="rgba(255, 255, 255, 0.05)"
+ backdropFilter="blur(10px)"
+ border="1px solid"
+ borderColor="rgba(255, 255, 255, 0.1)"
+ color="white"
+ _placeholder={{ color: 'gray.500' }}
+ _hover={{
+ borderColor: 'rgba(255, 255, 255, 0.2)',
+ }}
+ _focus={{
+ borderColor: 'purple.400',
+ boxShadow:
+ '0 0 0 1px var(--chakra-colors-purple-400), 0 0 12px rgba(139, 92, 246, 0.3)',
+ bg: 'rgba(255, 255, 255, 0.08)',
+ }}
+ />
+
+
+ }
+ onClick={onSendMessage}
+ isLoading={isProcessing}
+ isDisabled={!inputValue.trim() || isProcessing}
+ bgGradient="linear(to-r, blue.500, purple.600)"
+ color="white"
+ _hover={{
+ bgGradient: 'linear(to-r, blue.600, purple.700)',
+ boxShadow: '0 8px 20px rgba(139, 92, 246, 0.4)',
+ }}
+ _active={{
+ transform: 'translateY(0)',
+ boxShadow: '0 4px 12px rgba(139, 92, 246, 0.3)',
+ }}
+ />
+
+
+
+
+
+
+ Enter
+
+ 发送
+
+
+
+ Shift
+
+ +
+
+ Enter
+
+ 换行
+
+
+
+
+
+ );
+};
+
+export default ChatArea;