/** * ChatContainer - 通用 AI 对话容器组件 * * 基于 Ant Design X + X SDK 构建 * 支持多场景:AIA、PKB、Tool C * * 注意:由于 Ant Design X SDK 的复杂性,这里采用简化实现 * 直接管理消息状态,不使用 useXChat Hook */ import React, { useState, useCallback, useRef, useEffect } from 'react'; import { Bubble, Sender } from '@ant-design/x'; import type { ChatContainerProps, ChatMessage } from './types'; import type { BubbleItemType } from '@ant-design/x/es/bubble/interface'; import './styles/chat.css'; /** * ChatContainer 组件(简化实现) */ export const ChatContainer: React.FC = ({ conversationKey: _conversationKey, defaultMessages = [], providerConfig, customMessageRenderer, senderProps = {}, onMessageSent, onMessageReceived, onError, className = '', style = {}, }) => { // 如果没有默认消息,添加欢迎语 const initialMessages = defaultMessages.length > 0 ? defaultMessages : [{ id: 'welcome', role: 'assistant' as const, content: '您好!我是您的 AI 数据分析师。我可以帮您编写代码来清洗数据。试试说:"把年龄大于60的设为老年组"。', status: 'success' as const, timestamp: Date.now(), }]; const [messages, setMessages] = useState(initialMessages); const [isLoading, setIsLoading] = useState(false); const [inputValue, setInputValue] = useState(''); // 受控输入框 const messagesEndRef = useRef(null); // 用于滚动到底部 // 滚动到底部 const scrollToBottom = useCallback(() => { setTimeout(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, 100); }, []); // 消息变化时自动滚动 useEffect(() => { scrollToBottom(); }, [messages, scrollToBottom]); // 处理消息发送 const handleSend = useCallback(async (messageContent: string) => { if (!messageContent.trim()) return; // 防止发送空消息 // 🔑 立即清除输入框 setInputValue(''); // 1. 添加用户消息 const userMessage: ChatMessage = { id: Date.now(), role: 'user', content: messageContent, status: 'success', timestamp: Date.now(), }; setMessages(prev => [...prev, userMessage]); if (onMessageSent) { onMessageSent(userMessage); } // 2. 显示加载状态 const loadingMessage: ChatMessage = { id: 'loading', role: 'assistant', content: '正在思考...', status: 'loading', timestamp: Date.now(), }; setMessages(prev => [...prev, loadingMessage]); setIsLoading(true); try { // 3. 调用后端 API let response; if (providerConfig.requestFn) { response = await providerConfig.requestFn(messageContent, { messages }); } else { const res = await fetch(providerConfig.apiEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: messageContent }), }); if (!res.ok) throw new Error('API 请求失败'); response = await res.json(); } // 4. 移除加载消息,添加 AI 响应 const aiMessage: ChatMessage = { id: response.messageId || Date.now(), role: 'assistant', content: response.explanation || response.message || response.content || '', status: 'success', code: response.code, explanation: response.explanation, timestamp: Date.now(), metadata: response.metadata, }; setMessages(prev => prev.filter(m => m.id !== 'loading').concat(aiMessage)); if (onMessageReceived) { onMessageReceived(aiMessage); } } catch (error: any) { // 5. 错误处理 const errorMessage: ChatMessage = { id: Date.now(), role: 'assistant', content: `抱歉,处理您的请求时出现错误:${error.message}`, status: 'error', timestamp: Date.now(), }; setMessages(prev => prev.filter(m => m.id !== 'loading').concat(errorMessage)); if (onError) { onError(error); } } finally { setIsLoading(false); } }, [messages, providerConfig, onMessageSent, onMessageReceived, onError]); // 转换消息为 Bubble.List 所需格式 const bubbleItems: BubbleItemType[] = messages.map((msg) => ({ key: msg.id, role: msg.role, content: customMessageRenderer ? customMessageRenderer({ id: msg.id, message: msg, status: msg.status || 'success' }) : msg.content, status: msg.status, })); return (
{/* 消息列表 */}
{/* 滚动锚点 */}
{/* 输入框(受控模式) */}
); }; export default ChatContainer;