feat(dc): Complete Tool C Day 5 - AI Chat + Ant Design X Integration
Summary: - Upgrade to Ant Design 6.0.1 + install Ant Design X (2.1.0) + X SDK (2.1.0) - Develop frontend common capability layer: Chat component library (~968 lines) * ChatContainer.tsx - Core container component * MessageRenderer.tsx - Message renderer * CodeBlockRenderer.tsx - Code block renderer with syntax highlighting * Complete TypeScript types and documentation - Integrate ChatContainer into Tool C - Fix 7 critical UI issues: * AG Grid module registration error * UI refinement (borders, shadows, gradients) * Add AI welcome message * Auto-clear input field after sending * Remove page scrollbars * Manual code execution (not auto-run) * Support simple Q&A (new /ai/chat API) - Complete end-to-end testing - Update all documentation (4 status docs + 6 dev logs) Technical Stack: - Frontend: React 19 + Ant Design 6.0 + Ant Design X 2.1 - Components: Bubble, Sender from @ant-design/x - Total code: ~5418 lines Status: Tool C MVP completed, production ready
This commit is contained in:
162
frontend-v2/src/shared/components/Chat/ChatContainer.tsx
Normal file
162
frontend-v2/src/shared/components/Chat/ChatContainer.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* ChatContainer - 通用 AI 对话容器组件
|
||||
*
|
||||
* 基于 Ant Design X + X SDK 构建
|
||||
* 支持多场景:AIA、PKB、Tool C
|
||||
*
|
||||
* 注意:由于 Ant Design X SDK 的复杂性,这里采用简化实现
|
||||
* 直接管理消息状态,不使用 useXChat Hook
|
||||
*/
|
||||
|
||||
import React, { useState, useCallback } 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<ChatContainerProps> = ({
|
||||
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<ChatMessage[]>(initialMessages);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// 处理消息发送
|
||||
const handleSend = useCallback(async (messageContent: string) => {
|
||||
if (!messageContent.trim()) return; // 防止发送空消息
|
||||
|
||||
// 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 (
|
||||
<div className={`chat-container ${className}`} style={style}>
|
||||
{/* 消息列表 */}
|
||||
<div className="chat-messages">
|
||||
<Bubble.List
|
||||
items={bubbleItems}
|
||||
autoScroll={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 输入框 */}
|
||||
<div className="chat-input">
|
||||
<Sender
|
||||
placeholder="输入消息..."
|
||||
loading={isLoading}
|
||||
onSubmit={handleSend}
|
||||
{...senderProps}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatContainer;
|
||||
Reference in New Issue
Block a user